diff --git a/src/components/modals/stars/StarsBalanceModal.tsx b/src/components/modals/stars/StarsBalanceModal.tsx index 7f02d3c47..f41efe911 100644 --- a/src/components/modals/stars/StarsBalanceModal.tsx +++ b/src/components/modals/stars/StarsBalanceModal.tsx @@ -41,6 +41,7 @@ const TRANSACTION_TABS: TabWithProperties[] = [ { title: 'StarsTransactionsOutgoing' }, ]; const TRANSACTION_ITEM_CLASS = 'StarsTransactionItem'; +const SUBSCRIPTION_PURPOSE = 'subs'; export type OwnProps = { modal: TabState['starsBalanceModal']; @@ -69,15 +70,19 @@ const StarsBalanceModal = ({ const isOpen = Boolean(modal && starsBalanceState); - const { originStarsPayment, originReaction, originGift } = modal || {}; + 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; + || originGift?.gift.stars + || topup?.balanceNeeded; const starsNeeded = ongoingTransactionAmount ? ongoingTransactionAmount - (balance || 0) : undefined; const starsNeededText = useMemo(() => { - if (!starsNeeded || starsNeeded < 0) return undefined; const global = getGlobal(); if (originReaction) { @@ -98,11 +103,15 @@ const StarsBalanceModal = ({ return oldLang('StarsNeededTextGift', getUserFullName(user)); } - return undefined; - }, [starsNeeded, originReaction, originStarsPayment, originGift, oldLang]); + if (topup?.purpose === SUBSCRIPTION_PURPOSE) { + return oldLang('StarsNeededTextLink'); + } - const shouldShowItems = Boolean(history?.all?.transactions.length && !originStarsPayment && !originReaction); - const shouldSuggestGifting = !originStarsPayment && !originReaction; + return undefined; + }, [originReaction, originStarsPayment, originGift, topup?.purpose, oldLang]); + + const shouldShowItems = Boolean(history?.all?.transactions.length && !shouldOpenOnBuy); + const shouldSuggestGifting = !shouldOpenOnBuy; useEffect(() => { if (!isOpen) { @@ -113,13 +122,13 @@ const StarsBalanceModal = ({ }, [isOpen]); useEffect(() => { - if (ongoingTransactionAmount) { + if (shouldOpenOnBuy) { showBuyOptions(); return; } hideBuyOptions(); - }, [ongoingTransactionAmount]); + }, [shouldOpenOnBuy]); const tosText = useMemo(() => { if (!isOpen) return undefined; diff --git a/src/components/payment/Checkout.tsx b/src/components/payment/Checkout.tsx index 76a322882..54f02712e 100644 --- a/src/components/payment/Checkout.tsx +++ b/src/components/payment/Checkout.tsx @@ -40,7 +40,6 @@ export type OwnProps = { name?: string; phone?: string; shippingMethod?: string; - botName?: string; }; totalPrice?: number; needAddress?: boolean; diff --git a/src/components/payment/PaymentModal.tsx b/src/components/payment/PaymentModal.tsx index bf39c845a..f8402b63c 100644 --- a/src/components/payment/PaymentModal.tsx +++ b/src/components/payment/PaymentModal.tsx @@ -626,7 +626,6 @@ export default memo(withGlobal( temporaryPassword, isExtendedMedia, url, - botId, } = selectTabState(global).payment; const { invoice, nativeParams, nativeProvider } = form || {}; @@ -640,7 +639,7 @@ export default memo(withGlobal( const chat = inputInvoice && 'chatId' in inputInvoice ? selectChat(global, inputInvoice.chatId!) : undefined; const isProviderError = Boolean(invoice && (!providerName || !SUPPORTED_PROVIDERS.has(providerName))); const { needCardholderName, needCountry, needZip } = (nativeParams || {}); - const bot = botId ? selectUser(global, botId) : undefined; + const bot = form?.botId ? selectUser(global, form.botId) : undefined; const botName = getUserFullName(bot); return { diff --git a/src/global/actions/apiUpdaters/payments.ts b/src/global/actions/apiUpdaters/payments.ts index 29352bb6c..6ce43be25 100644 --- a/src/global/actions/apiUpdaters/payments.ts +++ b/src/global/actions/apiUpdaters/payments.ts @@ -1,6 +1,5 @@ import type { ActionReturnType } from '../../types'; -import { areDeepEqual } from '../../../util/areDeepEqual'; import { formatCurrencyAsString } from '../../../util/formatCurrency'; import * as langProvider from '../../../util/oldLangProvider'; import { addActionHandler, setGlobal } from '../../index'; @@ -17,13 +16,24 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { const { totalAmount, currency } = invoice; - actions.showNotification({ - tabId, - message: langProvider.oldTranslate('PaymentInfoHint', [ - formatCurrencyAsString(totalAmount, currency, langProvider.getTranslationFn().code), - form.title, - ]), - }); + if (paymentState.inputInvoice?.type === 'stars') { + actions.closeStarsBalanceModal({ tabId }); + actions.showNotification({ + message: langProvider.oldTranslate('StarsAcquiredInfo', paymentState.inputInvoice.stars), + title: langProvider.oldTranslate('StarsAcquired'), + icon: 'star', + tabId, + }); + actions.requestConfetti({ withStars: true, tabId }); + } else { + actions.showNotification({ + tabId, + message: langProvider.oldTranslate('PaymentInfoHint', [ + formatCurrencyAsString(totalAmount, currency, langProvider.getTranslationFn().code), + form.title, + ]), + }); + } setGlobal(global); @@ -69,8 +79,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { } const starsModalState = selectTabState(global, tabId).starsGiftModal; - if (starsModalState && starsModalState.isOpen - && areDeepEqual(inputInvoice.userId, starsModalState.forUserId)) { + if (starsModalState?.isOpen && inputInvoice.userId === starsModalState.forUserId) { global = updateTabState(global, { starsGiftModal: { ...starsModalState, @@ -78,22 +87,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { }, }, tabId); } - } - if (inputInvoice?.type === 'stars') { - const starsModalState = selectTabState(global, tabId).starsGiftModal; - - if (starsModalState && starsModalState.isOpen) { - global = updateTabState(global, { - starsGiftModal: { - ...starsModalState, - isCompleted: true, - }, - }, tabId); - } - } - - if (inputInvoice?.type === 'stars' || inputInvoice?.type === 'stargift') { actions.requestConfetti({ withStars: true, tabId }); } break; diff --git a/src/global/actions/ui/stars.ts b/src/global/actions/ui/stars.ts index 88e522aed..5dc9271cf 100644 --- a/src/global/actions/ui/stars.ts +++ b/src/global/actions/ui/stars.ts @@ -2,6 +2,7 @@ import type { ApiUserStarGift } from '../../../api/types'; import type { ActionReturnType } from '../../types'; import { getCurrentTabId } from '../../../util/establishMultitabRole'; +import * as langProvider from '../../../util/oldLangProvider'; import { getPrizeStarsTransactionFromGiveaway, getStarsTransactionFromGift } from '../../helpers/payments'; import { addActionHandler } from '../../index'; import { @@ -12,7 +13,13 @@ import { selectChatMessage, selectStarsPayment } from '../../selectors'; addActionHandler('processOriginStarsPayment', (global, actions, payload): ActionReturnType => { const { originData, status, tabId = getCurrentTabId() } = payload; - const { originStarsPayment, originReaction, originGift } = originData || {}; + const { + originStarsPayment, originReaction, originGift, topup, + } = originData || {}; + + if (!originStarsPayment && !originReaction && !originGift && !topup) { + return undefined; + } actions.closeStarsBalanceModal({ tabId }); @@ -105,9 +112,27 @@ addActionHandler('openStarsBalanceModal', (global, actions, payload): ActionRetu originStarsPayment, originReaction, originGift, + topup, + shouldIgnoreBalance, tabId = getCurrentTabId(), } = payload || {}; + const starBalance = global.stars?.balance; + + if (!shouldIgnoreBalance && starBalance && topup && topup.balanceNeeded <= starBalance) { + actions.showNotification({ + message: langProvider.oldTranslate('StarsTopupLinkEnough'), + actionText: langProvider.oldTranslate('StarsTopupLinkTopupAnyway'), + action: { + action: 'openStarsBalanceModal', + payload: { topup, shouldIgnoreBalance: true, tabId }, + }, + icon: 'star', + tabId, + }); + return undefined; + } + global = clearStarPayment(global, tabId); // Always refresh status on opening @@ -118,6 +143,7 @@ addActionHandler('openStarsBalanceModal', (global, actions, payload): ActionRetu originStarsPayment, originReaction, originGift, + topup, }, }, tabId); }); diff --git a/src/global/types.ts b/src/global/types.ts index 43963e12c..3e634cb78 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -875,6 +875,10 @@ export type TabState = { messageId: number; amount: number; }; + topup?: { + balanceNeeded: number; + purpose?: string; + }; }; giftInfoModal?: { @@ -2375,6 +2379,11 @@ export interface ActionPayloads { messageId: number; amount: number; }; + topup?: { + balanceNeeded: number; + purpose?: string; + }; + shouldIgnoreBalance?: boolean; } & WithTabId; closeStarsBalanceModal: WithTabId | undefined; diff --git a/src/util/deepLinkParser.ts b/src/util/deepLinkParser.ts index e891d7e0d..d43c3c46f 100644 --- a/src/util/deepLinkParser.ts +++ b/src/util/deepLinkParser.ts @@ -6,7 +6,7 @@ import { isUsernameValid } from './username'; export type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'addemoji' | 'setlanguage' | 'addtheme' | 'confirmphone' | 'socks' | 'proxy' | 'privatepost' | 'bg' | 'share' | 'msg' | 'msg_url' | -'invoice' | 'addlist' | 'boost' | 'giftcode' | 'message' | 'premium_offer' | 'premium_multigift'; +'invoice' | 'addlist' | 'boost' | 'giftcode' | 'message' | 'premium_offer' | 'premium_multigift' | 'stars_topup'; interface PublicMessageLink { type: 'publicMessageLink'; diff --git a/src/util/deeplink.ts b/src/util/deeplink.ts index ae6a0a35e..53a549c91 100644 --- a/src/util/deeplink.ts +++ b/src/util/deeplink.ts @@ -71,6 +71,7 @@ export const processDeepLink = (url: string): boolean => { openStoryViewerByUsername, processBoostParameters, checkGiftCode, + openStarsBalanceModal, } = actions; switch (method) { @@ -176,6 +177,15 @@ export const processDeepLink = (url: string): boolean => { break; } + case 'stars_topup': { + const { balance, purpose } = params; + const balanceNeeded = Number(balance); + if (!balanceNeeded || balanceNeeded < 0) return true; + + openStarsBalanceModal({ topup: { balanceNeeded, purpose } }); + break; + } + case 'boost': { const { channel, domain } = params; const isPrivate = Boolean(channel);