import type { TeactNode } from '../../../../lib/teact/teact'; import React, { memo, useMemo } from '../../../../lib/teact/teact'; import { getActions, getGlobal, withGlobal } from '../../../../global'; import type { ApiUser, } from '../../../../api/types'; import type { TabState } from '../../../../global/types'; import { getUserFullName } from '../../../../global/helpers'; import { selectUser } from '../../../../global/selectors'; import buildClassName from '../../../../util/buildClassName'; 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 Button from '../../../ui/Button'; import ConfirmDialog from '../../../ui/ConfirmDialog'; import Link from '../../../ui/Link'; 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 = { userFrom?: ApiUser; targetUser?: ApiUser; currentUserId?: string; starGiftMaxConvertPeriod?: number; }; const STICKER_SIZE = 120; const GiftInfoModal = ({ modal, userFrom, targetUser, currentUserId, starGiftMaxConvertPeriod, }: OwnProps & StateProps) => { const { closeGiftInfoModal, changeGiftVisibility, convertGiftToStars, openChatWithInfo, focusMessage, openGiftUpgradeModal, } = getActions(); const [isConvertConfirmOpen, openConvertConfirm, closeConvertConfirm] = useFlag(); const lang = useLang(); const oldLang = useOldLang(); const isOpen = Boolean(modal); const renderingModal = useCurrentOrPrev(modal); const { gift: typeGift } = renderingModal || {}; const isUserGift = typeGift && 'gift' in typeGift; const userGift = isUserGift ? typeGift : undefined; const isSender = userGift?.fromId === currentUserId; const canConvertDifference = (userGift && starGiftMaxConvertPeriod && ( userGift.date + starGiftMaxConvertPeriod - getServerTime() )) || 0; const conversionLeft = Math.ceil(canConvertDifference / 60 / 60 / 24); const gift = isUserGift ? typeGift.gift : typeGift; const giftSticker = gift && getStickerFromGift(gift); const canFocusUpgrade = Boolean(userGift?.upgradeMsgId); const canUpdate = Boolean(userGift?.messageId) && targetUser?.id === currentUserId && !canFocusUpgrade; const handleClose = useLastCallback(() => { closeGiftInfoModal(); }); const handleFocusUpgraded = useLastCallback(() => { if (!userGift?.upgradeMsgId || !targetUser) return; const { upgradeMsgId } = userGift; focusMessage({ chatId: targetUser.id, messageId: upgradeMsgId! }); handleClose(); }); const handleTriggerVisibility = useLastCallback(() => { const { messageId, isUnsaved } = userGift!; changeGiftVisibility({ messageId: messageId!, shouldUnsave: !isUnsaved }); handleClose(); }); const handleConvertToStars = useLastCallback(() => { const { messageId } = userGift!; convertGiftToStars({ messageId: messageId! }); closeConvertConfirm(); handleClose(); }); const handleOpenUpgradeModal = useLastCallback(() => { if (!userGift) return; openGiftUpgradeModal({ giftId: userGift.gift.id, gift: userGift }); }); const giftAttributes = useMemo(() => { return gift && getGiftAttributes(gift); }, [gift]); const renderFooterButton = useLastCallback(() => { if (canFocusUpgrade) { return ( ); } if (canUpdate && userGift?.alreadyPaidUpgradeStars && !userGift.upgradeMsgId) { return ( ); } return ( ); }); const modalData = useMemo(() => { if (!typeGift || !gift) { return undefined; } const { fromId, isNameHidden, starsToConvert, isUnsaved, isConverted, } = userGift || {}; const isVisibleForMe = isNameHidden && targetUser; const description = (() => { if (!userGift) return lang('GiftInfoSoldOutDescription'); if (userGift.upgradeMsgId) return lang('GiftInfoDescriptionUpgraded'); if (userGift.canUpgrade && userGift.alreadyPaidUpgradeStars) { return canUpdate ? lang('GiftInfoDescriptionFreeUpgrade') : lang('GiftInfoDescriptionFreeUpgradeOut', { user: getUserFullName(targetUser)! }); } if (!canUpdate && !isSender) return undefined; if (isConverted && starsToConvert) { return canUpdate ? lang('GiftInfoDescriptionConverted', { amount: formatInteger(starsToConvert!), }, { pluralValue: starsToConvert, withNodes: true, withMarkdown: true, }) : lang('GiftInfoDescriptionOutConverted', { amount: formatInteger(starsToConvert!), user: getUserFullName(targetUser)!, }, { pluralValue: starsToConvert, withNodes: true, withMarkdown: true, }); } if (userGift.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('GiftInfoDescriptionOut', { amount: starsToConvert, user: getUserFullName(targetUser)!, }, { withNodes: true, withMarkdown: true, pluralValue: starsToConvert || 0, }); })(); function getTitle() { if (gift?.type === 'starGiftUnique') return gift.title; if (!userGift) return lang('GiftInfoSoldOutTitle'); return canUpdate ? lang('GiftInfoReceived') : lang('GiftInfoTitle'); } const isUniqueGift = gift.type === 'starGiftUnique'; 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 (userGift?.date) { tableData.push([ lang('GiftInfoDate'), formatDateTimeToString(userGift.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 + (userGift?.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) { tableData.push([ lang('GiftInfoStatus'),
{lang('GiftInfoStatusNonUnique')} {canUpdate && {lang('GiftInfoUpgradeBadge')}}
, ]); } if (userGift?.message) { tableData.push([ undefined, renderTextWithEntities(userGift.message), ]); } } if (gift.type === 'starGiftUnique') { const { model, backdrop, pattern, originalDetails, } = giftAttributes || {}; const ownerName = gift.ownerName; tableData.push([ lang('GiftInfoOwner'), gift.ownerId ? { chatId: gift.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(); // User names does not need to be reactive const openChat = (id: string) => { openChatWithInfo({ id }); closeGiftInfoModal(); }; const recipient = selectUser(global, recipientId)!; const sender = senderId ? selectUser(global, senderId) : undefined; const formattedDate = formatDateTimeToString(date * 1000, lang.code, true); const recipientLink = ( // eslint-disable-next-line react/jsx-no-bind openChat(recipientId)} isPrimary> {getUserFullName(recipient)} ); let text: TeactNode | undefined; if (!sender || senderId === recipientId) { text = message ? lang('GiftInfoOriginalInfoText', { user: recipientLink, text: renderTextWithEntities(message), date: formattedDate, }, { withNodes: true, }) : lang('GiftInfoOriginalInfo', { user: recipientLink, date: formattedDate, }, { withNodes: true, }); } else { const senderLink = ( // eslint-disable-next-line react/jsx-no-bind openChat(sender.id)} isPrimary> {getUserFullName(sender)} ); text = message ? lang('GiftInfoOriginalInfoTextSender', { user: recipientLink, sender: senderLink, text: renderTextWithEntities(message), date: formattedDate, }, { withNodes: true, }) : lang('GiftInfoOriginalInfoSender', { user: recipientLink, date: formattedDate, sender: senderLink, }, { withNodes: true, }); } tableData.push([ undefined, {text}, ]); } } const footer = (
{canUpdate && (
{lang(isUnsaved ? 'GiftInfoHidden' : 'GiftInfoSaved', { link: {lang('GiftInfoSavedHide')}, }, { withNodes: true, })}
{isVisibleForMe && (
{lang('GiftInfoSenderHidden')}
)}
)} {renderFooterButton()}
); return { header: isUniqueGift ? uniqueGiftHeader : regularHeader, tableData, footer, }; }, [ typeGift, userGift, targetUser, giftSticker, lang, canUpdate, canConvertDifference, isSender, oldLang, gift, giftAttributes, renderFooterButton, ]); return ( <> {userGift && (
{lang('GiftInfoConvertDescription1', { amount: formatStarsAsText(lang, userGift.starsToConvert!), user: getUserFullName(userFrom)!, }, { 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 isUserGift = typeGift && 'gift' in typeGift; const fromId = isUserGift && typeGift.fromId; const userFrom = fromId ? selectUser(global, fromId) : undefined; const targetUser = modal?.userId ? selectUser(global, modal.userId) : undefined; return { userFrom, targetUser, currentUserId: global.currentUserId, starGiftMaxConvertPeriod: global.appConfig?.starGiftMaxConvertPeriod, }; }, )(GiftInfoModal));