import type { FC, TeactNode } from '../../../../lib/teact/teact'; import React, { memo, useMemo } from '../../../../lib/teact/teact'; import { getActions, getGlobal, withGlobal } from '../../../../global'; import type { ApiPeer, } from '../../../../api/types'; import type { TabState } from '../../../../global/types'; import { TME_LINK_PREFIX } from '../../../../config'; import { getHasAdminRight, getPeerTitle } from '../../../../global/helpers'; import { isApiPeerChat } from '../../../../global/helpers/peers'; import { selectPeer } from '../../../../global/selectors'; import buildClassName from '../../../../util/buildClassName'; import { copyTextToClipboard } from '../../../../util/clipboard'; import { formatDateTimeToString } from '../../../../util/dates/dateFormat'; import { formatStarsAsIcon, formatStarsAsText } from '../../../../util/localization/format'; import { CUSTOM_PEER_HIDDEN } from '../../../../util/objects/customPeer'; import { getServerTime } from '../../../../util/serverTime'; import { formatInteger, formatPercent } from '../../../../util/textFormat'; import { getGiftAttributes, getStickerFromGift } from '../../../common/helpers/gifts'; import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities'; import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev'; import useFlag from '../../../../hooks/useFlag'; import useLang from '../../../../hooks/useLang'; import useLastCallback from '../../../../hooks/useLastCallback'; import useOldLang from '../../../../hooks/useOldLang'; import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker'; import Avatar from '../../../common/Avatar'; import BadgeButton from '../../../common/BadgeButton'; import Icon from '../../../common/icons/Icon'; import Button from '../../../ui/Button'; import ConfirmDialog from '../../../ui/ConfirmDialog'; import DropdownMenu from '../../../ui/DropdownMenu'; import Link from '../../../ui/Link'; import MenuItem from '../../../ui/MenuItem'; import TableInfoModal, { type TableData } from '../../common/TableInfoModal'; import UniqueGiftHeader from '../UniqueGiftHeader'; import styles from './GiftInfoModal.module.scss'; export type OwnProps = { modal: TabState['giftInfoModal']; }; type StateProps = { fromPeer?: ApiPeer; targetPeer?: ApiPeer; currentUserId?: string; starGiftMaxConvertPeriod?: number; hasAdminRights?: boolean; }; const STICKER_SIZE = 120; const GiftInfoModal = ({ modal, fromPeer, targetPeer, currentUserId, starGiftMaxConvertPeriod, hasAdminRights, }: OwnProps & StateProps) => { const { closeGiftInfoModal, changeGiftVisibility, convertGiftToStars, openChatWithInfo, focusMessage, openGiftUpgradeModal, showNotification, openChatWithDraft, } = getActions(); const [isConvertConfirmOpen, openConvertConfirm, closeConvertConfirm] = useFlag(); const lang = useLang(); const oldLang = useOldLang(); const isOpen = Boolean(modal); const renderingModal = useCurrentOrPrev(modal); const renderingFromPeer = useCurrentOrPrev(fromPeer); const renderingTargetPeer = useCurrentOrPrev(targetPeer); const isTargetChat = renderingTargetPeer && isApiPeerChat(renderingTargetPeer); const { gift: typeGift } = renderingModal || {}; const isSavedGift = typeGift && 'gift' in typeGift; const savedGift = isSavedGift ? typeGift : undefined; const isSender = savedGift?.fromId === currentUserId; const canConvertDifference = (savedGift && starGiftMaxConvertPeriod && ( savedGift.date + starGiftMaxConvertPeriod - getServerTime() )) || 0; const conversionLeft = Math.ceil(canConvertDifference / 60 / 60 / 24); const gift = isSavedGift ? typeGift.gift : typeGift; const giftSticker = gift && getStickerFromGift(gift); const canFocusUpgrade = Boolean(savedGift?.upgradeMsgId); const canUpdate = !canFocusUpgrade && savedGift?.inputGift && ( isTargetChat ? hasAdminRights : renderingTargetPeer?.id === currentUserId ); const handleClose = useLastCallback(() => { closeGiftInfoModal(); }); const starGiftUniqueLink = useMemo(() => { const slug = gift?.type === 'starGiftUnique' ? gift.slug : undefined; if (!slug) return undefined; return `${TME_LINK_PREFIX}nft/${slug}`; }, [gift]); const handleCopyLink = useLastCallback(() => { if (!starGiftUniqueLink) return; copyTextToClipboard(starGiftUniqueLink); showNotification({ message: lang('LinkCopied'), }); }); const handleLinkShare = useLastCallback(() => { if (!starGiftUniqueLink) return; openChatWithDraft({ text: { text: starGiftUniqueLink } }); handleClose(); }); const handleFocusUpgraded = useLastCallback(() => { if (!savedGift?.upgradeMsgId || !renderingTargetPeer) return; const { upgradeMsgId } = savedGift; focusMessage({ chatId: renderingTargetPeer.id, messageId: upgradeMsgId! }); handleClose(); }); const handleTriggerVisibility = useLastCallback(() => { const { inputGift, isUnsaved } = savedGift!; changeGiftVisibility({ gift: inputGift!, shouldUnsave: !isUnsaved }); handleClose(); }); const handleConvertToStars = useLastCallback(() => { const { inputGift } = savedGift!; convertGiftToStars({ gift: inputGift! }); closeConvertConfirm(); handleClose(); }); const handleOpenUpgradeModal = useLastCallback(() => { if (!savedGift) return; openGiftUpgradeModal({ giftId: savedGift.gift.id, gift: savedGift }); }); const giftAttributes = useMemo(() => { return gift && getGiftAttributes(gift); }, [gift]); const SettingsMenuButton: FC<{ onTrigger: () => void; isMenuOpen?: boolean }> = useMemo(() => { return ({ onTrigger, isMenuOpen }) => ( ); }, [lang]); const renderFooterButton = useLastCallback(() => { if (canFocusUpgrade) { return ( ); } if (canUpdate && savedGift?.alreadyPaidUpgradeStars && !savedGift.upgradeMsgId) { return ( ); } return ( ); }); const modalData = useMemo(() => { if (!typeGift || !gift) { return undefined; } const { fromId, isNameHidden, starsToConvert, isUnsaved, isConverted, } = savedGift || {}; const isVisibleForMe = isNameHidden && renderingTargetPeer; const description = (() => { if (!savedGift) return lang('GiftInfoSoldOutDescription'); if (isTargetChat) return undefined; if (savedGift.upgradeMsgId) return lang('GiftInfoDescriptionUpgraded'); if (savedGift.canUpgrade && savedGift.alreadyPaidUpgradeStars) { return canUpdate ? lang('GiftInfoDescriptionFreeUpgrade') : lang('GiftInfoPeerDescriptionFreeUpgradeOut', { peer: getPeerTitle(lang, renderingTargetPeer!)! }); } if (!canUpdate && !isSender) return undefined; if (isConverted && starsToConvert) { return canUpdate ? lang('GiftInfoDescriptionConverted', { amount: formatInteger(starsToConvert!), }, { pluralValue: starsToConvert, withNodes: true, withMarkdown: true, }) : lang('GiftInfoPeerDescriptionOutConverted', { amount: formatInteger(starsToConvert!), peer: getPeerTitle(lang, renderingTargetPeer!)!, }, { pluralValue: starsToConvert, withNodes: true, withMarkdown: true, }); } if (savedGift.canUpgrade && canUpdate) { return lang('GiftInfoDescriptionUpgrade', { amount: formatInteger(starsToConvert!), }, { pluralValue: starsToConvert!, withNodes: true, withMarkdown: true, }); } return canUpdate ? lang('GiftInfoDescription', { amount: starsToConvert, }, { withNodes: true, withMarkdown: true, pluralValue: starsToConvert || 0, }) : lang('GiftInfoPeerDescriptionOut', { amount: starsToConvert, peer: getPeerTitle(lang, renderingTargetPeer!)!, }, { withNodes: true, withMarkdown: true, pluralValue: starsToConvert || 0, }); })(); function getTitle() { if (gift?.type === 'starGiftUnique') return gift.title; if (!savedGift) return lang('GiftInfoSoldOutTitle'); return canUpdate ? lang('GiftInfoReceived') : lang('GiftInfoTitle'); } const isUniqueGift = gift.type === 'starGiftUnique'; const contextMenu = ( {lang('CopyLink')} {lang('Share')} ); const uniqueGiftModalHeader = (
{isOpen && contextMenu}
); const uniqueGiftHeader = isUniqueGift && (
); const regularHeader = (

{getTitle()}

{description && (

{description}

)}
); const tableData: TableData = []; if (gift.type === 'starGift') { if ((fromId || isNameHidden)) { tableData.push([ lang('GiftInfoFrom'), fromId ? { chatId: fromId } : ( <> {oldLang(CUSTOM_PEER_HIDDEN.titleKey!)} ), ]); } if (savedGift?.date) { tableData.push([ lang('GiftInfoDate'), formatDateTimeToString(savedGift.date * 1000, lang.code, true), ]); } if (gift.firstSaleDate) { tableData.push([ lang('GiftInfoFirstSale'), formatDateTimeToString(gift.firstSaleDate * 1000, lang.code, true), ]); } if (gift.lastSaleDate) { tableData.push([ lang('GiftInfoLastSale'), formatDateTimeToString(gift.lastSaleDate * 1000, lang.code, true), ]); } const starsValue = gift.stars + (savedGift?.alreadyPaidUpgradeStars || 0); tableData.push([ lang('GiftInfoValue'),
{formatStarsAsIcon(lang, starsValue, { className: styles.starAmountIcon })} {canUpdate && canConvertDifference > 0 && Boolean(starsToConvert) && ( {lang('GiftInfoConvert', { amount: starsToConvert }, { pluralValue: starsToConvert })} )}
, ]); if (gift.availabilityTotal) { tableData.push([ lang('GiftInfoAvailability'), lang('GiftInfoAvailabilityValue', { count: gift.availabilityRemains || 0, total: gift.availabilityTotal, }, { pluralValue: gift.availabilityRemains || 0, }), ]); } if (gift.upgradeStars && !savedGift?.upgradeMsgId) { tableData.push([ lang('GiftInfoStatus'),
{lang('GiftInfoStatusNonUnique')} {canUpdate && {lang('GiftInfoUpgradeBadge')}}
, ]); } if (savedGift?.message) { tableData.push([ undefined, renderTextWithEntities(savedGift.message), ]); } } if (gift.type === 'starGiftUnique') { const { ownerName, ownerAddress, ownerId } = gift; const { model, backdrop, pattern, originalDetails, } = giftAttributes || {}; if (ownerAddress) { tableData.push([ lang('GiftInfoOwner'), { copyTextToClipboard(ownerAddress); showNotification({ message: { key: 'WalletAddressCopied' }, icon: 'copy', }); }} > {ownerAddress} , ]); } else { tableData.push([ lang('GiftInfoOwner'), ownerId ? { chatId: ownerId } : ownerName || '', ]); } if (model) { tableData.push([ lang('GiftAttributeModel'), {model.name}{formatPercent(model.rarityPercent)} , ]); } if (backdrop) { tableData.push([ lang('GiftAttributeBackdrop'), {backdrop.name}{formatPercent(backdrop.rarityPercent)} , ]); } if (pattern) { tableData.push([ lang('GiftAttributeSymbol'), {pattern.name}{formatPercent(pattern.rarityPercent)} , ]); } tableData.push([ lang('GiftInfoAvailability'), lang('GiftInfoIssued', { issued: gift.issuedCount, total: gift.totalCount, }), ]); if (originalDetails) { const { date, recipientId, message, senderId, } = originalDetails; const global = getGlobal(); // Peer titles do not need to be reactive const openChat = (id: string) => { openChatWithInfo({ id }); closeGiftInfoModal(); }; const recipient = selectPeer(global, recipientId)!; const sender = senderId ? selectPeer(global, senderId) : undefined; const formattedDate = formatDateTimeToString(date * 1000, lang.code, true); const recipientLink = ( // eslint-disable-next-line react/jsx-no-bind openChat(recipientId)} isPrimary> {getPeerTitle(lang, recipient)} ); 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 = ( // eslint-disable-next-line react/jsx-no-bind openChat(sender.id)} isPrimary> {getPeerTitle(lang, sender)} ); 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, }); } tableData.push([ undefined, {text}, ]); } } const footer = (
{canUpdate && (
{lang(`GiftInfo${isTargetChat ? 'Channel' : ''}${isUnsaved ? 'Hidden' : 'Saved'}`, { link: ( {lang(`GiftInfoSaved${isUnsaved ? 'Show' : 'Hide'}`)} ), }, { withNodes: true, })}
{isVisibleForMe && (
{lang('GiftInfoSenderHidden')}
)}
)} {renderFooterButton()}
); return { modalHeader: isUniqueGift ? uniqueGiftModalHeader : undefined, header: isUniqueGift ? uniqueGiftHeader : regularHeader, tableData, footer, }; }, [ typeGift, savedGift, renderingTargetPeer, giftSticker, lang, canUpdate, canConvertDifference, isSender, oldLang, gift, giftAttributes, renderFooterButton, isTargetChat, SettingsMenuButton, isOpen, ]); return ( <> {savedGift && (
{lang('GiftInfoPeerConvertDescription', { amount: formatStarsAsText(lang, savedGift.starsToConvert!), peer: getPeerTitle(lang, renderingFromPeer!)!, }, { withNodes: true, withMarkdown: true, })}
{canConvertDifference > 0 && (
{lang('GiftInfoConvertDescriptionPeriod', { count: conversionLeft, }, { withNodes: true, withMarkdown: true, pluralValue: conversionLeft, })}
)}
{lang('GiftInfoConvertDescription2')}
)} ); }; export default memo(withGlobal( (global, { modal }): StateProps => { const typeGift = modal?.gift; const isSavedGift = typeGift && 'gift' in typeGift; const fromId = isSavedGift && typeGift.fromId; const fromPeer = fromId ? selectPeer(global, fromId) : undefined; const targetPeer = modal?.peerId ? selectPeer(global, modal.peerId) : undefined; const chat = targetPeer && isApiPeerChat(targetPeer) ? targetPeer : undefined; const hasAdminRights = chat && getHasAdminRight(chat, 'postMessages'); return { fromPeer, targetPeer, currentUserId: global.currentUserId, starGiftMaxConvertPeriod: global.appConfig?.starGiftMaxConvertPeriod, hasAdminRights, }; }, )(GiftInfoModal));