import { memo, useEffect, useMemo, useState } from '@teact'; import { getActions, getGlobal, withGlobal } from '../../../global'; import type { ApiStarTopupOption } from '../../../api/types'; import type { GlobalState, TabState } from '../../../global/types'; import type { RegularLangKey } from '../../../types/language'; import { PAID_MESSAGES_PURPOSE, STARS_CURRENCY_CODE, TON_CURRENCY_CODE, TON_TOPUP_URL_DEFAULT, TON_USD_RATE_DEFAULT, } from '../../../config'; import { getChatTitle, getUserFullName } from '../../../global/helpers'; import { getPeerTitle } from '../../../global/helpers/peers'; import { selectChat, selectIsPremiumPurchaseBlocked, selectUser } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import { convertCurrencyFromBaseUnit, convertTonToUsd, formatCurrencyAsString } from '../../../util/formatCurrency'; import renderText from '../../common/helpers/renderText'; import useFlag from '../../../hooks/useFlag'; import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; import useOldLang from '../../../hooks/useOldLang'; import Icon from '../../common/icons/Icon'; import SafeLink from '../../common/SafeLink'; import Button from '../../ui/Button'; import InfiniteScroll from '../../ui/InfiniteScroll'; import Modal from '../../ui/Modal'; import TabList, { type TabWithProperties } from '../../ui/TabList'; import Transition from '../../ui/Transition'; import ParticlesHeader from '../common/ParticlesHeader.tsx'; import BalanceBlock from './BalanceBlock'; import StarTopupOptionList from './StarTopupOptionList'; import StarsSubscriptionItem from './subscription/StarsSubscriptionItem'; import StarsTransactionItem from './transaction/StarsTransactionItem'; import styles from './StarsBalanceModal.module.scss'; const TRANSACTION_TYPES = ['all', 'inbound', 'outbound'] as const; const TRANSACTION_TABS_KEYS: RegularLangKey[] = [ 'StarsTransactionsAll', 'StarsTransactionsIncoming', 'StarsTransactionsOutgoing', ]; const TRANSACTION_ITEM_CLASS = 'StarsTransactionItem'; const SUBSCRIPTION_PURPOSE = 'subs'; export type OwnProps = { modal: TabState['starsBalanceModal']; }; type StateProps = { starsBalanceState?: GlobalState['stars']; tonBalanceState?: GlobalState['ton']; canBuyPremium?: boolean; shouldForceHeight?: boolean; tonUsdRate?: number; tonTopupUrl: string; }; const StarsBalanceModal = ({ modal, starsBalanceState, tonBalanceState, canBuyPremium, shouldForceHeight, tonUsdRate, tonTopupUrl, }: OwnProps & StateProps) => { const { closeStarsBalanceModal, loadStarsTransactions, loadStarsSubscriptions, openStarsGiftingPickerModal, openInvoice, openUrl, } = getActions(); const currency = modal?.currency || STARS_CURRENCY_CODE; const currentState = currency === TON_CURRENCY_CODE ? tonBalanceState : starsBalanceState; const { balance, history } = currentState || {}; const { subscriptions } = (currency === TON_CURRENCY_CODE && starsBalanceState) || {}; const oldLang = useOldLang(); const lang = useLang(); const [isHeaderHidden, setHeaderHidden] = useState(true); const [selectedTabIndex, setSelectedTabIndex] = useState(0); const [areBuyOptionsShown, showBuyOptions, hideBuyOptions] = useFlag(); const isOpen = Boolean(modal && (starsBalanceState || tonBalanceState)); const { originStarsPayment, originReaction, originGift, topup, } = modal || {}; const shouldOpenOnBuy = originStarsPayment || originReaction || originGift || topup; const ongoingTransactionAmount = originStarsPayment?.form?.invoice?.totalAmount || originStarsPayment?.subscriptionInfo?.subscriptionPricing?.amount || originReaction?.amount || originGift?.gift.stars || topup?.balanceNeeded; const starsNeeded = ongoingTransactionAmount ? ongoingTransactionAmount - (balance?.amount || 0) : undefined; const starsNeededText = useMemo(() => { const global = getGlobal(); if (originReaction) { const channel = selectChat(global, originReaction.chatId); if (!channel) return undefined; return oldLang('StarsNeededTextReactions', getChatTitle(oldLang, channel)); } if (originStarsPayment) { const bot = originStarsPayment.form?.botId ? selectUser(global, originStarsPayment.form.botId) : undefined; if (!bot) return undefined; return oldLang('StarsNeededText', getUserFullName(bot)); } if (originGift) { const peer = selectUser(global, originGift.peerId); if (!peer) return undefined; return oldLang('StarsNeededTextGift', getPeerTitle(lang, peer)); } if (topup?.purpose === SUBSCRIPTION_PURPOSE) { return oldLang('StarsNeededTextLink'); } if (topup?.purpose === PAID_MESSAGES_PURPOSE) { return lang('StarsNeededTextSendPaidMessages', undefined, { withMarkdown: true, withNodes: true, }); } return undefined; }, [originReaction, originStarsPayment, originGift, topup?.purpose, lang, oldLang]); const shouldShowItems = Boolean(history?.all?.transactions.length && !shouldOpenOnBuy); const shouldSuggestGifting = !shouldOpenOnBuy; const transactionTabs: TabWithProperties[] = useMemo(() => { return TRANSACTION_TABS_KEYS.map((key) => ({ title: lang(key), })); }, [lang]); useEffect(() => { if (!isOpen) { setHeaderHidden(true); setSelectedTabIndex(0); hideBuyOptions(); } }, [isOpen]); useEffect(() => { if (shouldOpenOnBuy) { showBuyOptions(); return; } hideBuyOptions(); }, [shouldOpenOnBuy]); const tosText = useMemo(() => { if (!isOpen) return undefined; const text = oldLang('lng_credits_summary_options_about'); const parts = text.split('{link}'); return [ parts[0], , parts[1], ]; }, [isOpen, oldLang]); const renderStarsSection = () => { return ( <> {canBuyPremium && !areBuyOptionsShown && ( )} {canBuyPremium && !areBuyOptionsShown && shouldSuggestGifting && ( )} {areBuyOptionsShown && starsBalanceState?.topupOptions && ( )} ); }; const renderTonSection = () => { const tonAmount = convertCurrencyFromBaseUnit(balance?.amount || 0, TON_CURRENCY_CODE); return ( <>
{tonAmount}
{Boolean(tonUsdRate) && ( {`≈ ${formatCurrencyAsString( convertTonToUsd(balance?.amount || 0, tonUsdRate), 'USD', lang.code, )}`} )}
); }; function handleScroll(e: React.UIEvent) { const { scrollTop } = e.currentTarget; setHeaderHidden(scrollTop <= 150); } const handleLoadMoreTransactions = useLastCallback(() => { loadStarsTransactions({ type: TRANSACTION_TYPES[selectedTabIndex], isTon: currency === TON_CURRENCY_CODE, }); }); const handleLoadMoreSubscriptions = useLastCallback(() => { loadStarsSubscriptions(); }); const openStarsGiftingPickerModalHandler = useLastCallback(() => { openStarsGiftingPickerModal({}); }); const handleBuyStars = useLastCallback((option: ApiStarTopupOption) => { openInvoice({ type: 'stars', stars: option.stars, currency: option.currency, amount: option.amount, }); }); const handleTonTopUp = useLastCallback(() => { openUrl({ url: tonTopupUrl, shouldSkipModal: true }); }); return (
{currency !== TON_CURRENCY_CODE && }

{oldLang('TelegramStars')}

{currency === TON_CURRENCY_CODE ? renderTonSection() : renderStarsSection()}
{areBuyOptionsShown && (
{tosText}
)} {currency === TON_CURRENCY_CODE && (
{lang('TonModalHint')}
)} {shouldShowItems && Boolean(subscriptions?.list.length) && (

{oldLang('StarMySubscriptions')}

{subscriptions?.list.map((subscription) => ( ))} {subscriptions?.nextOffset && ( )}
)} {shouldShowItems && (
{history?.[TRANSACTION_TYPES[selectedTabIndex]]?.transactions.map((transaction) => ( ))}
)}
); }; export default memo(withGlobal( (global, { modal }): StateProps => { const shouldForceHeight = modal?.currency === TON_CURRENCY_CODE ? Boolean(global.ton?.history?.all?.transactions.length) : Boolean(global.stars?.history?.all?.transactions.length); return { shouldForceHeight, starsBalanceState: global.stars, tonBalanceState: global.ton, canBuyPremium: !selectIsPremiumPurchaseBlocked(global), tonUsdRate: global.appConfig?.tonUsdRate || TON_USD_RATE_DEFAULT, tonTopupUrl: global.appConfig?.tonTopupUrl || TON_TOPUP_URL_DEFAULT, }; }, )(StarsBalanceModal));