Gifts: Support remove gift message (#6404)

Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
This commit is contained in:
Alexander Zinchuk 2025-11-06 11:36:44 +01:00
parent f8c61646b1
commit cf8a933b4c
28 changed files with 435 additions and 62 deletions

View File

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

View File

@ -422,7 +422,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
if (action instanceof GramJs.MessageActionStarGiftUnique) {
const {
upgrade, transferred, saved, refunded, gift, canExportAt, transferStars, fromId, peer, savedId,
resaleAmount, prepaidUpgrade,
resaleAmount, prepaidUpgrade, dropOriginalDetailsStars,
} = action;
const starGift = buildApiStarGift(gift);
@ -443,6 +443,9 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
peerId: peer && getApiChatIdFromMtpPeer(peer),
savedId: savedId !== undefined ? buildApiPeerId(savedId, 'user') : undefined,
resaleAmount: resaleAmount ? buildApiCurrencyAmount(resaleAmount) : undefined,
dropOriginalDetailsStars: dropOriginalDetailsStars !== undefined
? toJSNumber(dropOriginalDetailsStars)
: undefined,
};
}
if (action instanceof GramJs.MessageActionPaidMessagesPrice) {

View File

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

View File

@ -781,6 +781,12 @@ export function buildInputInvoice(invoice: ApiRequestInputInvoice) {
});
}
case 'stargiftDropOriginalDetails': {
return new GramJs.InputInvoiceStarGiftDropOriginalDetails({
stargift: buildInputSavedStarGift(invoice.inputSavedGift),
});
}
case 'stargiftPrepaidUpgrade': {
return new GramJs.InputInvoiceStarGiftPrepaidUpgrade({
peer: buildInputPeer(invoice.peer.id, invoice.peer.accessHash),

View File

@ -269,6 +269,7 @@ export interface ApiMessageActionStarGiftUnique extends ActionMediaType {
peerId?: string;
savedId?: string;
resaleAmount?: ApiTypeCurrencyAmount;
dropOriginalDetailsStars?: number;
}
export interface ApiMessageActionChannelJoined extends ActionMediaType {

View File

@ -399,6 +399,11 @@ export type ApiInputInvoiceStarGiftTransfer = {
recipientId: string;
};
export type ApiInputInvoiceStarGiftDropOriginalDetails = {
type: 'stargiftDropOriginalDetails';
inputSavedGift: ApiInputSavedStarGift;
};
export type ApiInputInvoiceStarGiftPrepaidUpgrade = {
type: 'stargiftPrepaidUpgrade';
peerId: string;
@ -409,7 +414,7 @@ export type ApiInputInvoice = ApiInputInvoiceMessage | ApiInputInvoiceSlug | Api
| ApiInputInvoiceGiftCode | ApiInputInvoicePremiumGiftStars | ApiInputInvoiceStars | ApiInputInvoiceStarsGift
| ApiInputInvoiceStarsGiveaway | ApiInputInvoiceStarGift | ApiInputInvoiceChatInviteSubscription
| ApiInputInvoiceStarGiftUpgrade | ApiInputInvoiceStarGiftTransfer | ApiInputInvoiceStarGiftResale
| ApiInputInvoiceStarGiftPrepaidUpgrade;
| ApiInputInvoiceStarGiftDropOriginalDetails | ApiInputInvoiceStarGiftPrepaidUpgrade;
/* Used for Invoice request */
export type ApiRequestInputInvoiceMessage = {
@ -479,6 +484,11 @@ export type ApiRequestInputInvoiceStarGiftTransfer = {
recipient: ApiPeer;
};
export type ApiRequestInputInvoiceStarGiftDropOriginalDetails = {
type: 'stargiftDropOriginalDetails';
inputSavedGift: ApiRequestInputSavedStarGift;
};
export type ApiRequestInputInvoiceStarGiftPrepaidUpgrade = {
type: 'stargiftPrepaidUpgrade';
peer: ApiPeer;
@ -490,7 +500,7 @@ export type ApiRequestInputInvoice = ApiRequestInputInvoiceMessage | ApiRequestI
| ApiRequestInputInvoiceChatInviteSubscription | ApiRequestInputInvoiceStarGift
| ApiRequestInputInvoiceStarGiftUpgrade | ApiRequestInputInvoiceStarGiftTransfer
| ApiRequestInputInvoicePremiumGiftStars | ApiRequestInputInvoiceStarGiftResale
| ApiRequestInputInvoiceStarGiftPrepaidUpgrade;
| ApiRequestInputInvoiceStarGiftDropOriginalDetails | ApiRequestInputInvoiceStarGiftPrepaidUpgrade;
export interface ApiUniqueStarGiftValueInfo {
isLastSaleOnFragment?: true;

View File

@ -111,6 +111,7 @@ export interface ApiSavedStarGift {
isConverted?: boolean; // Local field, used for Action Message
upgradeMsgId?: number; // Local field, used for Action Message
localTag?: number; // Local field, used for key in list
dropOriginalDetailsStars?: number;
}
export type StarGiftAttributeIdModel = {
@ -245,6 +246,7 @@ export interface ApiStarsTransaction {
isGiftResale?: true;
paidMessages?: number;
isPostsSearch?: true;
isDropOriginalDetails?: true;
isPrepaidUpgrade?: true;
}

View File

@ -1558,6 +1558,10 @@
"GiftTransferConfirmButton" = "Transfer for {amount}";
"GiftTransferConfirmButtonFree" = "Transfer";
"GiftTransferSuccessMessage" = "You have successfully gifted {gift} to {peer}.";
"RemoveGiftDescriptionTitle" = "Remove Description";
"RemoveGiftDescriptionConfirmText" = "Do you want to permanently remove this description from your gift?";
"RemoveGiftDescriptionButton" = "Remove for {amount}";
"RemoveGiftDescriptionSuccessMessage" = "Gift's Description Removed!";
"GiftUpgradeUniqueTitle" = "Unique";
"GiftUpgradeUniqueDescription" = "Turn your gift into a unique collectible that you can transfer or auction.";
"GiftUpgradeTransferableTitle" = "Transferable";
@ -2301,6 +2305,8 @@
"TitleGiftLocked" = "Gift Locked";
"GiftLockedMessage" = "This gift is currently only available to earlier Telegram users. It will unlock for your account in about **{relativeDate}**.";
"QuickPreview" = "Quick Preview";
"DropOriginalDetailsTransaction" = "Removed Gift Description";
"StarGiftReasonDropOriginalDetails" = "Removed Description";
"GiftAnUpgradeButton" = "Gift an Upgrade";
"GiftPrepaidUpgradeTransactionTitle" = "Gift Upgrade";
"ActionStarGiftPrepaidUpgraded" = "{user} turned the gift into a unique collectible";

View File

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

View File

@ -0,0 +1,75 @@
import type { TeactNode } from '../../../lib/teact/teact';
import type { ApiPeer, ApiStarGiftAttributeOriginalDetails } from '../../../api/types';
import { getPeerTitle } from '../../../global/helpers/peers';
import { formatDateTimeToString } from '../../../util/dates/dateFormat';
import { type LangFn } from '../../../util/localization';
import { renderTextWithEntities } from './renderTextWithEntities';
import Link from '../../ui/Link';
type GiftOriginalInfoOptions = {
originalDetails: ApiStarGiftAttributeOriginalDetails;
recipient: ApiPeer;
sender?: ApiPeer;
onOpenChat: (peerId: string) => void;
lang: LangFn;
};
export function renderGiftOriginalInfo({
originalDetails,
recipient,
sender,
onOpenChat,
lang,
}: GiftOriginalInfoOptions): TeactNode | undefined {
const { recipientId, senderId, date, message } = originalDetails;
const formattedDate = formatDateTimeToString(date * 1000, lang.code, true);
const recipientLink = (
<Link onClick={() => onOpenChat(recipientId)} isPrimary>
{getPeerTitle(lang, recipient)}
</Link>
);
if (!sender || senderId === recipientId) {
return message
? lang('GiftInfoPeerOriginalInfoText', {
peer: recipientLink,
text: renderTextWithEntities(message),
date: formattedDate,
}, {
withNodes: true,
})
: lang('GiftInfoPeerOriginalInfo', {
peer: recipientLink,
date: formattedDate,
}, {
withNodes: true,
});
}
const senderLink = (
<Link onClick={() => onOpenChat(sender.id)} isPrimary>
{getPeerTitle(lang, sender)}
</Link>
);
return message
? lang('GiftInfoPeerOriginalInfoTextSender', {
peer: recipientLink,
sender: senderLink,
text: renderTextWithEntities(message),
date: formattedDate,
}, {
withNodes: true,
})
: lang('GiftInfoPeerOriginalInfoSender', {
peer: recipientLink,
date: formattedDate,
sender: senderLink,
}, {
withNodes: true,
});
}

View File

@ -22,6 +22,7 @@ import FrozenAccountModal from './frozenAccount/FrozenAccountModal.async';
import PremiumGiftModal from './gift/GiftModal.async';
import GiftInfoModal from './gift/info/GiftInfoModal.async';
import GiftLockedModal from './gift/locked/GiftLockedModal.async';
import GiftDescriptionRemoveModal from './gift/message/GiftDescriptionRemoveModal.async';
import GiftRecipientPicker from './gift/recipient/GiftRecipientPicker.async';
import GiftResalePriceComposerModal from './gift/resale/GiftResalePriceComposerModal.async';
import GiftStatusInfoModal from './gift/status/GiftStatusInfoModal.async';
@ -97,6 +98,7 @@ type ModalKey = keyof Pick<TabState,
'giftStatusInfoModal' |
'giftTransferModal' |
'giftTransferConfirmModal' |
'giftDescriptionRemoveModal' |
'chatRefundModal' |
'priceConfirmModal' |
'isFrozenAccountModalOpen' |
@ -159,6 +161,7 @@ const MODALS: ModalRegistry = {
sharePreparedMessageModal: SharePreparedMessageModal,
giftTransferModal: GiftTransferModal,
giftTransferConfirmModal: GiftTransferConfirmModal,
giftDescriptionRemoveModal: GiftDescriptionRemoveModal,
chatRefundModal: ChatRefundModal,
priceConfirmModal: PriceConfirmModal,
isFrozenAccountModalOpen: FrozenAccountModal,

View File

@ -49,7 +49,7 @@
.modalContent {
position: relative;
max-height: min(92vh, 48rem) !important;
max-height: min(97vh, 48rem) !important;
}
.headerSplitButton {
@ -197,3 +197,14 @@
margin-inline-start: 0.25rem;
color: var(--color-primary);
}
.messageContainer {
position: relative;
display: flex;
gap: 0.5rem;
align-items: center;
}
.removeMessageButton {
color: var(--color-primary) !important;
}

View File

@ -1,4 +1,3 @@
import type { TeactNode } from '../../../../lib/teact/teact';
import { memo, useMemo, useRef, useState } from '../../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../../global';
@ -24,6 +23,7 @@ import {
import { CUSTOM_PEER_HIDDEN } from '../../../../util/objects/customPeer';
import { getServerTime } from '../../../../util/serverTime';
import { formatPercent } from '../../../../util/textFormat';
import { renderGiftOriginalInfo } from '../../../common/helpers/giftOriginalInfo';
import { getGiftAttributes, getStickerFromGift } from '../../../common/helpers/gifts';
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
@ -98,6 +98,7 @@ const GiftInfoModal = ({
openGiftInfoValueModal,
updateResaleGiftsFilter,
openGiftInMarket,
openGiftDescriptionRemoveModal,
} = getActions();
const [isConvertConfirmOpen, openConvertConfirm, closeConvertConfirm] = useFlag();
@ -280,6 +281,19 @@ const GiftInfoModal = ({
handleClose();
});
const handleRemoveMessage = useLastCallback(() => {
if (!savedGift?.inputGift || !savedGift.dropOriginalDetailsStars || !gift || !giftAttributes) return;
const { originalDetails } = giftAttributes;
if (!originalDetails) return;
openGiftDescriptionRemoveModal({
gift: savedGift,
price: savedGift.dropOriginalDetailsStars,
details: originalDetails,
});
});
const handleOpenUpgradeModal = useLastCallback(() => {
if (!savedGift) return;
const giftOwnerId = renderingTargetPeer?.id;
@ -761,9 +775,7 @@ const GiftInfoModal = ({
}
if (originalDetails) {
const {
date, recipientId, message, senderId,
} = originalDetails;
const { recipientId, senderId } = originalDetails;
const global = getGlobal(); // Peer titles do not need to be reactive
const openChat = (id: string) => {
@ -774,54 +786,29 @@ const GiftInfoModal = ({
const recipient = selectPeer(global, recipientId)!;
const sender = senderId ? selectPeer(global, senderId) : undefined;
const formattedDate = formatDateTimeToString(date * 1000, lang.code, true);
const recipientLink = (
<Link onClick={() => openChat(recipientId)} isPrimary>
{getPeerTitle(lang, recipient)}
</Link>
);
let text: TeactNode | undefined;
if (!sender || senderId === recipientId) {
text = message ? lang('GiftInfoPeerOriginalInfoText', {
peer: recipientLink,
text: renderTextWithEntities(message),
date: formattedDate,
}, {
withNodes: true,
}) : lang('GiftInfoPeerOriginalInfo', {
peer: recipientLink,
date: formattedDate,
}, {
withNodes: true,
});
} else {
const senderLink = (
<Link onClick={() => openChat(sender.id)} isPrimary>
{getPeerTitle(lang, sender)}
</Link>
);
text = message ? lang('GiftInfoPeerOriginalInfoTextSender', {
peer: recipientLink,
sender: senderLink,
text: renderTextWithEntities(message),
date: formattedDate,
}, {
withNodes: true,
}) : lang('GiftInfoPeerOriginalInfoSender', {
peer: recipientLink,
date: formattedDate,
sender: senderLink,
}, {
withNodes: true,
});
}
const text = renderGiftOriginalInfo({
originalDetails, recipient, sender, onOpenChat: openChat, lang,
});
tableData.push([
undefined,
<span>{text}</span>,
<div className={styles.messageContainer}>
<div>
{text}
</div>
{Boolean(savedGift?.dropOriginalDetailsStars) && (
<Button
round
className={styles.removeMessageButton}
size="smaller"
color="translucent"
ariaLabel="Delete original details"
onClick={handleRemoveMessage}
>
<Icon name="delete" />
</Button>
)}
</div>,
]);
}
}

View File

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

View File

@ -0,0 +1,14 @@
.confirmText {
margin-bottom: 1.5rem;
}
.giftDescription {
margin-bottom: 1rem;
padding: 0.375rem;
border: 0.0625rem solid var(--color-borders);
border-radius: 0.25rem;
text-align: center;
background-color: var(--color-background-secondary);
}

View File

@ -0,0 +1,94 @@
import { memo } from '../../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../../global';
import type { ApiPeer } from '../../../../api/types';
import type { TabState } from '../../../../global/types';
import { selectPeer } from '../../../../global/selectors';
import { formatStarsAsIcon } from '../../../../util/localization/format';
import { renderGiftOriginalInfo } from '../../../common/helpers/giftOriginalInfo';
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
import useLang from '../../../../hooks/useLang';
import useLastCallback from '../../../../hooks/useLastCallback';
import ConfirmDialog from '../../../ui/ConfirmDialog';
import styles from './GiftDescriptionRemoveModal.module.scss';
export type OwnProps = {
modal: TabState['giftDescriptionRemoveModal'];
};
type StateProps = {
senderPeer?: ApiPeer;
recipientPeer?: ApiPeer;
};
const GiftDescriptionRemoveModal = ({
modal, senderPeer, recipientPeer,
}: OwnProps & StateProps) => {
const {
closeGiftDescriptionRemoveModal, removeGiftDescription, openChat,
} = getActions();
const lang = useLang();
const isOpen = Boolean(modal);
const renderingModal = useCurrentOrPrev(modal);
const renderingSenderPeer = useCurrentOrPrev(senderPeer);
const renderingRecipientPeer = useCurrentOrPrev(recipientPeer);
const openChatHandler = useLastCallback((id: string) => {
closeGiftDescriptionRemoveModal();
openChat({ id });
});
const handleConfirm = useLastCallback(() => {
if (!renderingModal?.gift.inputGift || !renderingModal.price) return;
removeGiftDescription({
gift: renderingModal.gift.inputGift,
price: renderingModal.price,
});
});
if (!renderingModal || !renderingRecipientPeer) return undefined;
const { price, details } = renderingModal;
const description = renderGiftOriginalInfo({
originalDetails: details, recipient: renderingRecipientPeer,
sender: renderingSenderPeer, onOpenChat: openChatHandler, lang,
});
return (
<ConfirmDialog
isOpen={isOpen}
title={lang('RemoveGiftDescriptionTitle')}
onClose={closeGiftDescriptionRemoveModal}
confirmLabel={lang('RemoveGiftDescriptionButton', {
amount: formatStarsAsIcon(lang, price, { asFont: true }),
}, { withNodes: true })}
confirmHandler={handleConfirm}
>
<div className={styles.confirmText}>{lang('RemoveGiftDescriptionConfirmText')}</div>
{Boolean(description) && (
<div className={styles.giftDescription}>
{description}
</div>
)}
</ConfirmDialog>
);
};
export default memo(
withGlobal<OwnProps>((global, { modal }): Complete<StateProps> => {
const senderPeer = modal?.details.senderId ? selectPeer(global, modal.details.senderId) : undefined;
const recipientPeer = modal?.details.recipientId ? selectPeer(global, modal.details.recipientId) : undefined;
return {
senderPeer,
recipientPeer,
};
})(GiftDescriptionRemoveModal),
);

View File

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

View File

@ -83,7 +83,8 @@ const StarsTransactionItem = ({ transaction, className }: OwnProps) => {
avatarPeer = customPeer;
}
if (transaction.isGiftUpgrade && transaction.starGift?.type === 'starGiftUnique') {
if ((transaction.isGiftUpgrade || transaction.isDropOriginalDetails)
&& transaction.starGift?.type === 'starGiftUnique') {
description = lang('GiftUnique', { title: transaction.starGift.title, number: transaction.starGift.number });
}

View File

@ -98,7 +98,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
}
const {
giveawayPostId, photo, amount, isGiftUpgrade, starGift, isGiftResale,
giveawayPostId, photo, amount, isGiftUpgrade, isDropOriginalDetails, starGift, isGiftResale,
starRefCommision,
} = transaction;
@ -116,7 +116,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
const title = getTransactionTitle(oldLang, lang, transaction);
const messageLink = peer && transaction.messageId && !isGiftUpgrade
const messageLink = peer && transaction.messageId && !isGiftUpgrade && !isDropOriginalDetails
? getMessageLink(peer, undefined, transaction.messageId) : undefined;
const giveawayMessageLink = peer && giveawayPostId ? getMessageLink(peer, undefined, giveawayPostId) : undefined;
@ -131,7 +131,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
: oldLang('Media', mediaAmount);
const description = transaction.description
|| (isGiftUpgrade && starGift?.type === 'starGiftUnique' ? starGift.title : undefined)
|| ((isGiftUpgrade || isDropOriginalDetails) && starGift?.type === 'starGiftUnique' ? starGift.title : undefined)
|| (media ? mediaText : undefined);
const shouldDisplayAvatar = !media && !sticker && !transaction.isPostsSearch;
@ -231,6 +231,13 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
]);
}
if (isDropOriginalDetails) {
tableData.push([
oldLang('StarGiftReason'),
lang('StarGiftReasonDropOriginalDetails'),
]);
}
if (isGiftResale) {
tableData.push([
oldLang('StarGiftReason'),
@ -253,7 +260,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
peerLabel = oldLang('Stars.Transaction.Via');
}
if (!transaction.isPostsSearch) {
if (!transaction.isPostsSearch && !isDropOriginalDetails) {
tableData.push([
peerLabel,
peerId ? { chatId: peerId } : toName || '',

View File

@ -1117,6 +1117,17 @@ addActionHandler('transferGift', (global, actions, payload): ActionReturnType =>
payInputStarInvoice(global, invoice, transferStars, tabId);
});
addActionHandler('removeGiftDescription', (global, actions, payload): ActionReturnType => {
const { gift, price, tabId = getCurrentTabId() } = payload;
const invoice: ApiInputInvoice = {
type: 'stargiftDropOriginalDetails',
inputSavedGift: gift,
};
payInputStarInvoice(global, invoice, price, tabId);
});
addActionHandler('upgradePrepaidGift', (global, actions, payload): ActionReturnType => {
const { peerId, hash, stars, tabId = getCurrentTabId() } = payload;

View File

@ -3,8 +3,8 @@ import type { ActionReturnType } from '../../types';
import { formatCurrencyAsString } from '../../../util/formatCurrency';
import * as langProvider from '../../../util/oldLangProvider';
import { getPeerTitle } from '../../helpers/peers';
import { addActionHandler, setGlobal } from '../../index';
import { updateStarsBalance } from '../../reducers';
import { addActionHandler, getGlobal, setGlobal } from '../../index';
import { removeGiftInfoOriginalDetails, updateStarsBalance } from '../../reducers';
import { updateTabState } from '../../reducers/tabs';
import { selectPeer, selectTabState } from '../../selectors';
@ -176,6 +176,22 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
actions.reloadPeerSavedGifts({ peerId: global.currentUserId });
}
if (inputInvoice?.type === 'stargiftDropOriginalDetails') {
global = getGlobal();
global = removeGiftInfoOriginalDetails(global, tabId);
setGlobal(global);
actions.closeGiftDescriptionRemoveModal({ tabId });
actions.showNotification({
message: { key: 'RemoveGiftDescriptionSuccessMessage' },
tabId,
});
if (global.currentUserId) {
actions.reloadPeerSavedGifts({ peerId: global.currentUserId });
}
}
if (inputInvoice?.type === 'stargiftPrepaidUpgrade') {
actions.reloadPeerSavedGifts({ peerId: inputInvoice.peerId });

View File

@ -266,6 +266,7 @@ addActionHandler('openGiftInfoModalFromMessage', async (global, actions, payload
canExportAt: uniqueGift?.canExportAt,
savedId: action.savedId,
transferStars: uniqueGift?.transferStars,
dropOriginalDetailsStars: uniqueGift?.dropOriginalDetailsStars,
prepaidUpgradeHash: starGift?.prepaidUpgradeHash,
};
@ -457,6 +458,22 @@ addActionHandler('openGiftTransferConfirmModal', (global, actions, payload): Act
addTabStateResetterAction('closeGiftTransferConfirmModal', 'giftTransferConfirmModal');
addActionHandler('openGiftDescriptionRemoveModal', (global, actions, payload): ActionReturnType => {
const {
gift, price, details, tabId = getCurrentTabId(),
} = payload;
return updateTabState(global, {
giftDescriptionRemoveModal: {
gift,
price,
details,
},
}, tabId);
});
addTabStateResetterAction('closeGiftDescriptionRemoveModal', 'giftDescriptionRemoveModal');
addActionHandler('updateSelectedGiftCollection', (global, actions, payload): ActionReturnType => {
const { peerId, collectionId, tabId = getCurrentTabId() } = payload;
const tabState = selectTabState(global, tabId);

View File

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

View File

@ -0,0 +1,48 @@
import type { ApiSavedStarGift } from '../../api/types';
import type { GlobalState } from '../types';
import { getCurrentTabId } from '../../util/establishMultitabRole';
import { updateTabState } from './tabs';
export function removeGiftInfoOriginalDetails<T extends GlobalState>(
global: T,
tabId: number = getCurrentTabId(),
): T {
const tabState = global.byTabId[tabId];
const { giftInfoModal } = tabState;
if (!giftInfoModal) {
return global;
}
const typeGift = giftInfoModal.gift;
const isSavedGift = typeGift && 'gift' in typeGift;
if (!isSavedGift) {
return global;
}
const savedGift = typeGift;
const innerGift = savedGift.gift;
if (innerGift.type !== 'starGiftUnique') {
return global;
}
const updatedInnerGift = {
...innerGift,
attributes: innerGift.attributes?.filter((attr) => attr.type !== 'originalDetails'),
};
const updatedGift: ApiSavedStarGift = {
...savedGift,
dropOriginalDetailsStars: undefined,
gift: updatedInnerGift,
};
return updateTabState(global, {
giftInfoModal: {
...giftInfoModal,
gift: updatedGift,
},
}, tabId);
}

View File

@ -14,3 +14,4 @@ export * from './stories';
export * from './translations';
export * from './peers';
export * from './topics';
export * from './gifts';

View File

@ -45,6 +45,7 @@ import type {
ApiSendMessageAction,
ApiSessionData,
ApiStarGift,
ApiStarGiftAttributeOriginalDetails,
ApiStarGiftUnique,
ApiStarsSubscription,
ApiStarsTransaction,
@ -2661,12 +2662,22 @@ export interface ActionPayloads {
transferStars?: number;
recipientId: string;
} & WithTabId;
removeGiftDescription: {
gift: ApiInputSavedStarGift;
price: number;
} & WithTabId;
closeGiftTransferModal: WithTabId | undefined;
openGiftTransferConfirmModal: {
gift: ApiSavedStarGift;
recipientId: string;
} & WithTabId;
closeGiftTransferConfirmModal: WithTabId | undefined;
openGiftDescriptionRemoveModal: {
gift: ApiSavedStarGift;
price: number;
details: ApiStarGiftAttributeOriginalDetails;
} & WithTabId;
closeGiftDescriptionRemoveModal: WithTabId | undefined;
updateSelectedGiftCollection: {
peerId: string;
collectionId: number;

View File

@ -42,6 +42,7 @@ import type {
ApiStarGift,
ApiStarGiftAttribute,
ApiStarGiftAttributeCounter,
ApiStarGiftAttributeOriginalDetails,
ApiStarGiftUnique,
ApiStarGiveawayOption,
ApiStarsSubscription,
@ -854,6 +855,12 @@ export type TabState = {
recipientId: string;
};
giftDescriptionRemoveModal?: {
gift: ApiSavedStarGift;
price: number;
details: ApiStarGiftAttributeOriginalDetails;
};
giftUpgradeModal?: {
sampleAttributes: ApiStarGiftAttribute[];
recipientId?: string;

View File

@ -1292,6 +1292,9 @@ export interface LangPair {
'GiftTransferTitle': undefined;
'GiftTransferTON': undefined;
'GiftTransferConfirmButtonFree': undefined;
'RemoveGiftDescriptionTitle': undefined;
'RemoveGiftDescriptionConfirmText': undefined;
'RemoveGiftDescriptionSuccessMessage': undefined;
'GiftUpgradeUniqueTitle': undefined;
'GiftUpgradeUniqueDescription': undefined;
'GiftUpgradeTransferableTitle': undefined;
@ -1717,6 +1720,8 @@ export interface LangPair {
'ConfirmBuyGiftForTonDescription': undefined;
'TitleGiftLocked': undefined;
'QuickPreview': undefined;
'DropOriginalDetailsTransaction': undefined;
'StarGiftReasonDropOriginalDetails': undefined;
'GiftAnUpgradeButton': undefined;
'GiftPrepaidUpgradeTransactionTitle': undefined;
'ActionStarGiftPrepaidUpgradedYou': undefined;
@ -2191,6 +2196,9 @@ export interface LangPairWithVariables<V = LangVariable> {
'gift': V;
'peer': V;
};
'RemoveGiftDescriptionButton': {
'amount': V;
};
'GiftPeerUpgradeText': {
'peer': V;
};