From 8433012a88eaca5d65afa7cc46d79a205b6360ef Mon Sep 17 00:00:00 2001 From: zubiden <19638254+zubiden@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:52:30 +0200 Subject: [PATCH] Stars Transaction: Refactor modal (#4900) --- src/api/gramjs/apiBuilders/messages.ts | 17 +- src/api/gramjs/apiBuilders/payments.ts | 8 +- src/api/gramjs/methods/index.ts | 2 +- src/api/gramjs/methods/payments.ts | 33 ++- src/api/types/messages.ts | 3 + src/api/types/payments.ts | 11 +- src/bundles/extra.ts | 2 +- src/components/common/Avatar.tsx | 3 + src/components/main/Main.tsx | 14 +- src/components/middle/ActionMessage.tsx | 103 ++++--- src/components/modals/ModalContainer.tsx | 5 +- .../modals/stars/StarGiftInfoModal.async.tsx | 18 -- .../stars/StarGiftInfoModal.module.scss | 32 --- .../modals/stars/StarGiftInfoModal.tsx | 150 ---------- .../modals/stars/StarsBalanceModal.tsx | 2 +- .../modals/stars/StarsPaymentModal.tsx | 2 +- .../PaidMediaThumb.module.scss | 0 .../{ => transaction}/PaidMediaThumb.tsx | 14 +- .../StarsTransactionItem.module.scss | 0 .../StarsTransactionItem.tsx | 36 +-- .../StarsTransactionModal.async.tsx | 18 ++ .../StarsTransactionModal.module.scss | 69 +++++ .../transaction/StarsTransactionModal.tsx | 213 ++++++++++++++ src/components/payment/ReceiptModal.tsx | 269 ++++-------------- src/global/actions/api/payments.ts | 50 ++-- src/global/actions/ui/payments.ts | 17 +- src/global/helpers/payments.ts | 29 +- src/global/reducers/payments.ts | 42 +-- src/global/types.ts | 29 +- src/lib/gramjs/tl/apiTl.js | 3 +- src/lib/gramjs/tl/static/api.json | 1 + src/types/index.ts | 1 + 32 files changed, 623 insertions(+), 573 deletions(-) delete mode 100644 src/components/modals/stars/StarGiftInfoModal.async.tsx delete mode 100644 src/components/modals/stars/StarGiftInfoModal.module.scss delete mode 100644 src/components/modals/stars/StarGiftInfoModal.tsx rename src/components/modals/stars/{ => transaction}/PaidMediaThumb.module.scss (100%) rename src/components/modals/stars/{ => transaction}/PaidMediaThumb.tsx (85%) rename src/components/modals/stars/{ => transaction}/StarsTransactionItem.module.scss (100%) rename src/components/modals/stars/{ => transaction}/StarsTransactionItem.tsx (72%) create mode 100644 src/components/modals/stars/transaction/StarsTransactionModal.async.tsx create mode 100644 src/components/modals/stars/transaction/StarsTransactionModal.module.scss create mode 100644 src/components/modals/stars/transaction/StarsTransactionModal.tsx diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index 2192554c1..e2f954184 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -370,6 +370,7 @@ function buildAction( let isGiveaway: boolean | undefined; let isUnclaimed: boolean | undefined; let pluralValue: number | undefined; + let transactionId: string | undefined; let targetUserIds = 'users' in action ? action.users && action.users.map((id) => buildApiPeerId(id, 'user')) @@ -509,6 +510,7 @@ function buildAction( text = 'Notification.WebAppSentData'; translationValues.push(action.text); } else if (action instanceof GramJs.MessageActionGiftPremium) { + type = 'giftPremium'; text = isOutgoing ? 'ActionGiftOutbound' : 'ActionGiftInbound'; if (isOutgoing) { translationValues.push('%gift_payment_amount%'); @@ -561,6 +563,7 @@ function buildAction( text = 'BoostingGiveawayJustStarted'; translationValues.push('%action_origin%'); } else if (action instanceof GramJs.MessageActionGiftCode) { + type = 'giftCode'; text = isOutgoing ? 'ActionGiftOutbound' : 'BoostingReceivedGiftNoName'; slug = action.slug; months = action.months; @@ -621,7 +624,8 @@ function buildAction( translationValues.unshift('%action_origin%'); } } else if (action instanceof GramJs.MessageActionGiftStars) { - text = isOutgoing ? 'ActionGiftOutbound' : 'BoostingReceivedGiftNoName'; + type = 'giftStars'; + text = isOutgoing ? 'ActionGiftOutbound' : targetPeerId ? 'ActionGiftInbound' : 'BoostingReceivedGiftNoName'; if (isOutgoing) { translationValues.push('%gift_payment_amount%'); } else { @@ -629,10 +633,20 @@ function buildAction( } if (targetPeerId) { targetUserIds.push(targetPeerId); + targetChatId = targetPeerId; } + + if (action.cryptoCurrency) { + giftCryptoInfo = { + currency: action.cryptoCurrency, + amount: action.cryptoAmount!.toJSNumber(), + }; + } + currency = action.currency; amount = action.amount.toJSNumber(); stars = action.stars.toJSNumber(); + transactionId = action.transactionId; } else { text = 'ChatList.UnsupportedMessage'; } @@ -664,6 +678,7 @@ function buildAction( isTopicAction, isUnclaimed, pluralValue, + transactionId, }; } diff --git a/src/api/gramjs/apiBuilders/payments.ts b/src/api/gramjs/apiBuilders/payments.ts index 72a82fcab..3d48fb0c3 100644 --- a/src/api/gramjs/apiBuilders/payments.ts +++ b/src/api/gramjs/apiBuilders/payments.ts @@ -61,7 +61,10 @@ export function buildApiReceipt(receipt: GramJs.payments.TypePaymentReceipt): Ap return { type: 'stars', currency, - botId: buildApiPeerId(botId, 'user'), + peer: { + type: 'peer', + id: buildApiPeerId(botId, 'user'), + }, date, text, title, @@ -426,7 +429,7 @@ export function buildApiStarsTransactionPeer(peer: GramJs.TypeStarsTransactionPe export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction): ApiStarsTransaction { const { - date, id, peer, stars, description, photo, title, refund, extendedMedia, failed, msgId, pending, + date, id, peer, stars, description, photo, title, refund, extendedMedia, failed, msgId, pending, gift, } = transaction; if (photo) { @@ -448,6 +451,7 @@ export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction): hasFailed: failed, isPending: pending, messageId: msgId, + isGift: gift, extendedMedia: boughtExtendedMedia, }; } diff --git a/src/api/gramjs/methods/index.ts b/src/api/gramjs/methods/index.ts index b7679fe65..56badbd71 100644 --- a/src/api/gramjs/methods/index.ts +++ b/src/api/gramjs/methods/index.ts @@ -102,7 +102,7 @@ export { validateRequestedInfo, sendPaymentForm, getPaymentForm, getReceipt, fetchPremiumPromo, fetchTemporaryPaymentPassword, applyBoost, fetchBoostList, fetchBoostStatus, fetchGiveawayInfo, fetchMyBoosts, applyGiftCode, checkGiftCode, getPremiumGiftCodeOptions, launchPrepaidGiveaway, fetchStarsStatus, fetchStarsTopupOptions, fetchStarsTransactions, - sendStarPaymentForm, getStarsGiftOptions, + sendStarPaymentForm, getStarsGiftOptions, fetchStarsTransactionById, } from './payments'; export * from './fragment'; diff --git a/src/api/gramjs/methods/payments.ts b/src/api/gramjs/methods/payments.ts index 7ee774505..ad129d232 100644 --- a/src/api/gramjs/methods/payments.ts +++ b/src/api/gramjs/methods/payments.ts @@ -459,16 +459,19 @@ export async function fetchStarsStatus() { } export async function fetchStarsTransactions({ + peer, offset, isInbound, isOutbound, }: { + peer?: ApiPeer; offset?: string; isInbound?: true; isOutbound?: true; }) { + const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf(); const result = await invokeRequest(new GramJs.payments.GetStarsTransactions({ - peer: new GramJs.InputPeerSelf(), + peer: inputPeer, offset, inbound: isInbound, outbound: isOutbound, @@ -490,6 +493,34 @@ export async function fetchStarsTransactions({ }; } +export async function fetchStarsTransactionById({ + id, peer, +}: { + id: string; + peer?: ApiPeer; +}) { + const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf(); + const result = await invokeRequest(new GramJs.payments.GetStarsTransactionsByID({ + peer: inputPeer, + id: [new GramJs.InputStarsTransaction({ + id, + })], + })); + + if (!result?.history?.[0]) { + return undefined; + } + + const users = result.users.map(buildApiUser).filter(Boolean); + const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean); + + return { + users, + chats, + transaction: buildApiStarsTransaction(result.history[0]), + }; +} + export async function fetchStarsTopupOptions() { const result = await invokeRequest(new GramJs.payments.GetStarsTopupOptions()); diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts index a92407fe1..fd18cee29 100644 --- a/src/api/types/messages.ts +++ b/src/api/types/messages.ts @@ -397,6 +397,9 @@ export interface ApiAction { | 'joinedChannel' | 'chatBoost' | 'receipt' + | 'giftStars' + | 'giftPremium' + | 'giftCode' | 'other'; photo?: ApiPhoto; amount?: number; diff --git a/src/api/types/payments.ts b/src/api/types/payments.ts index 80c95a9e9..7c8184239 100644 --- a/src/api/types/payments.ts +++ b/src/api/types/payments.ts @@ -3,7 +3,7 @@ import type { ApiInvoiceContainer } from '../../types'; import type { ApiWebDocument } from './bots'; import type { ApiChat } from './chats'; import type { - ApiDocument, ApiMessageEntity, ApiPaymentCredentials, BoughtPaidMedia, MediaContent, + ApiDocument, ApiMessageEntity, ApiPaymentCredentials, BoughtPaidMedia, } from './messages'; import type { PrepaidGiveaway, StatisticsOverviewPercentage } from './statistics'; import type { ApiUser } from './users'; @@ -63,8 +63,7 @@ export interface ApiLabeledPrice { export interface ApiReceiptStars { type: 'stars'; - botId?: string; - peer?: ApiStarsTransactionPeer; + peer: ApiStarsTransactionPeer; date: number; title?: string; text?: string; @@ -273,18 +272,20 @@ export type ApiStarsTransactionPeer = | ApiStarsTransactionPeerPeer; export interface ApiStarsTransaction { - id: string; + id?: string; peer: ApiStarsTransactionPeer; messageId?: number; stars: number; isRefund?: true; + isGift?: true; + isMyGift?: true; // Used only for outgoing star gift messages hasFailed?: true; isPending?: true; date: number; title?: string; description?: string; photo?: ApiWebDocument; - extendedMedia?: MediaContent[]; + extendedMedia?: BoughtPaidMedia[]; } export interface ApiStarTopupOption { diff --git a/src/bundles/extra.ts b/src/bundles/extra.ts index 64f8ed796..1c9b1b414 100644 --- a/src/bundles/extra.ts +++ b/src/bundles/extra.ts @@ -25,10 +25,10 @@ export { default as PremiumLimitReachedModal } from '../components/main/premium/ export { default as StatusPickerMenu } from '../components/left/main/StatusPickerMenu'; export { default as BoostModal } from '../components/modals/boost/BoostModal'; export { default as GiftCodeModal } from '../components/modals/giftcode/GiftCodeModal'; -export { default as StarGiftInfoModal } from '../components/modals/stars/StarGiftInfoModal'; export { default as ChatlistModal } from '../components/modals/chatlist/ChatlistModal'; export { default as StarsBalanceModal } from '../components/modals/stars/StarsBalanceModal'; export { default as StarPaymentModal } from '../components/modals/stars/StarsPaymentModal'; +export { default as StarsTransactionInfoModal } from '../components/modals/stars/transaction/StarsTransactionModal'; export { default as AboutAdsModal } from '../components/common/AboutAdsModal'; export { default as AboutMonetizationModal } from '../components/common/AboutMonetizationModal'; diff --git a/src/components/common/Avatar.tsx b/src/components/common/Avatar.tsx index f5c8da813..5a02fefb8 100644 --- a/src/components/common/Avatar.tsx +++ b/src/components/common/Avatar.tsx @@ -26,6 +26,7 @@ import { isUserId, } from '../../global/helpers'; import buildClassName, { createClassNameBuilder } from '../../util/buildClassName'; +import buildStyle from '../../util/buildStyle'; import { getFirstLetters } from '../../util/textFormat'; import { getPeerColorClass } from './helpers/peerColor'; import renderText from './helpers/renderText'; @@ -225,6 +226,7 @@ const Avatar: FC = ({ const isRoundedRect = (isCustomPeer && peer.isAvatarSquare) || (isForum && !((withStory || withStorySolid) && realPeer?.hasStories)); const isPremiumGradient = isCustomPeer && peer.withPremiumGradient; + const customColor = isCustomPeer && peer.customPeerAvatarColor; const fullClassName = buildClassName( `Avatar size-${size}`, @@ -273,6 +275,7 @@ const Avatar: FC = ({ data-peer-id={realPeer?.id} data-test-sender-id={IS_TEST ? realPeer?.id : undefined} aria-label={typeof content === 'string' ? author : undefined} + style={buildStyle(customColor && `--color-user: ${customColor}`)} onClick={handleClick} onMouseDown={handleMouseDown} > diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 59e4d3c5e..7acc160cf 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -1,6 +1,5 @@ import '../../global/actions/all'; -import type { FC } from '../../lib/teact/teact'; import React, { memo, useEffect, useLayoutEffect, useRef, useState, @@ -9,7 +8,6 @@ import { addExtraClass } from '../../lib/teact/teact-dom'; import { getActions, getGlobal, withGlobal } from '../../global'; import type { - ApiChat, ApiChatFolder, ApiMessage, ApiUser, @@ -74,7 +72,6 @@ import ReactionPicker from '../middle/message/reactions/ReactionPicker.async'; import MessageListHistoryHandler from '../middle/MessageListHistoryHandler'; import MiddleColumn from '../middle/MiddleColumn'; import ModalContainer from '../modals/ModalContainer'; -import StarGiftInfoModal from '../modals/stars/StarGiftInfoModal'; import PaymentModal from '../payment/PaymentModal.async'; import ReceiptModal from '../payment/ReceiptModal.async'; import RightColumn from '../right/RightColumn'; @@ -106,7 +103,6 @@ export interface OwnProps { type StateProps = { isMasterTab?: boolean; - chat?: ApiChat; currentUserId?: string; isLeftColumnOpen: boolean; isMiddleColumnOpen: boolean; @@ -144,12 +140,10 @@ type StateProps = { isPaymentModalOpen?: boolean; isReceiptModalOpen?: boolean; isReactionPickerOpen: boolean; - isAppendModalOpen?: boolean; isGiveawayModalOpen?: boolean; isDeleteMessageModalOpen?: boolean; isPremiumGiftingPickerModal?: boolean; isStarsGiftingPickerModal?: boolean; - isStarGiftInfoModal?: boolean; isCurrentUserPremium?: boolean; noRightColumnAnimation?: boolean; withInterfaceAnimations?: boolean; @@ -162,7 +156,7 @@ const CALL_BUNDLE_LOADING_DELAY_MS = 5000; // 5 sec // eslint-disable-next-line @typescript-eslint/naming-convention let DEBUG_isLogged = false; -const Main: FC = ({ +const Main = ({ isMobile, isLeftColumnOpen, isMiddleColumnOpen, @@ -201,7 +195,6 @@ const Main: FC = ({ isDeleteMessageModalOpen, isPremiumGiftingPickerModal, isStarsGiftingPickerModal, - isStarGiftInfoModal, isPaymentModalOpen, isReceiptModalOpen, isReactionPickerOpen, @@ -211,7 +204,7 @@ const Main: FC = ({ noRightColumnAnimation, isSynced, currentUserId, -}) => { +}: OwnProps & StateProps) => { const { initMain, loadAnimatedEmojis, @@ -584,7 +577,6 @@ const Main: FC = ({ {isGiveawayModalOpen && } {isPremiumGiftingPickerModal && } {isStarsGiftingPickerModal && } - {isStarGiftInfoModal && } @@ -627,7 +619,6 @@ export default memo(withGlobal( deleteMessageModal, giftingModal, starsGiftingModal, - starGiftInfoModal, isMasterTab, payment, limitReachedModal, @@ -685,7 +676,6 @@ export default memo(withGlobal( isDeleteMessageModalOpen: Boolean(deleteMessageModal), isPremiumGiftingPickerModal: giftingModal?.isOpen, isStarsGiftingPickerModal: starsGiftingModal?.isOpen, - isStarGiftInfoModal: starGiftInfoModal?.isOpen, limitReached: limitReachedModal?.limit, isPaymentModalOpen: payment.isPaymentModalOpen, isReceiptModalOpen: Boolean(payment.receipt), diff --git a/src/components/middle/ActionMessage.tsx b/src/components/middle/ActionMessage.tsx index 22d666f11..8fcce92f4 100644 --- a/src/components/middle/ActionMessage.tsx +++ b/src/components/middle/ActionMessage.tsx @@ -13,7 +13,7 @@ import type { FocusDirection, ThreadId } from '../../types'; import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage'; import { - getChatTitle, getMessageHtmlId, getSenderTitle, isJoinedChannelMessage, + getChatTitle, getMessageHtmlId, isJoinedChannelMessage, } from '../../global/helpers'; import { getMessageReplyInfo } from '../../global/helpers/replies'; import { @@ -35,7 +35,6 @@ import useContextMenuHandlers from '../../hooks/useContextMenuHandlers'; import useEnsureMessage from '../../hooks/useEnsureMessage'; import useFlag from '../../hooks/useFlag'; import { useIsIntersecting, useOnIntersect } from '../../hooks/useIntersectionObserver'; -import useLang from '../../hooks/useLang'; import useOldLang from '../../hooks/useOldLang'; import useShowTransition from '../../hooks/useShowTransition'; import useFocusMessage from './message/hooks/useFocusMessage'; @@ -104,11 +103,10 @@ const ActionMessage: FC = ({ onPinnedIntersectionChange, }) => { const { - openPremiumModal, requestConfetti, checkGiftCode, getReceipt, openStarGiftInfoModal, + openPremiumModal, requestConfetti, checkGiftCode, getReceipt, openStarsTransactionFromGift, } = getActions(); const oldLang = useOldLang(); - const lang = useLang(); // eslint-disable-next-line no-null/no-null const ref = useRef(null); @@ -131,11 +129,11 @@ const ActionMessage: FC = ({ const noAppearanceAnimation = appearanceOrder <= 0; const [isShown, markShown] = useFlag(noAppearanceAnimation); - const isGift = Boolean(message.content.action?.text.startsWith('ActionGift')); - const isGiftCode = Boolean(message.content.action?.text.startsWith('BoostingReceivedGift')); + const isPremiumGift = message.content.action?.type === 'giftPremium'; + const isGiftCode = message.content.action?.type === 'giftCode'; const isSuggestedAvatar = message.content.action?.type === 'suggestProfilePhoto' && message.content.action!.photo; const isJoinedMessage = isJoinedChannelMessage(message); - const hasStars = Boolean(message.content.action?.stars); + const isStarsGift = message.content.action?.type === 'giftStars'; useEffect(() => { if (noAppearanceAnimation) { @@ -149,7 +147,7 @@ const ActionMessage: FC = ({ const shouldShowConfettiRef = useRef((() => { const isUnread = memoFirstUnreadIdRef?.current && message.id >= memoFirstUnreadIdRef.current; - return isGift && !message.isOutgoing && isUnread; + return isPremiumGift && !message.isOutgoing && isUnread; })()); useEffect(() => { @@ -201,10 +199,9 @@ const ActionMessage: FC = ({ }; const handleStarGiftClick = () => { - openStarGiftInfoModal({ - toUserId: targetUserIds?.[0], - stars: message.content.action!.stars, - date: message.date, + openStarsTransactionFromGift({ + chatId: message.chatId, + messageId: message.id, }); }; @@ -245,10 +242,10 @@ const ActionMessage: FC = ({ function renderGift() { return ( = ({ noLoop nonInteractive /> - {hasStars ? oldLang('Stars', message.content.action?.stars) - : oldLang('ActionGiftPremiumTitle')} - - {hasStars ? oldLang('ActionGiftStarsSubtitleYou') - : oldLang('ActionGiftPremiumSubtitle', oldLang('Months', message.content.action?.months, 'i'))} + {oldLang('ActionGiftPremiumTitle')} + + {oldLang('ActionGiftPremiumSubtitle', oldLang('Months', message.content.action?.months, 'i'))} {oldLang('ActionGiftPremiumView')} @@ -277,7 +272,7 @@ const ActionMessage: FC = ({ className="action-message-gift action-message-gift-code" tabIndex={0} role="button" - onClick={hasStars ? handleStarGiftClick : handleGiftCodeClick} + onClick={handleGiftCodeClick} > = ({ noLoop nonInteractive /> - {hasStars ? oldLang('Stars', message.content.action?.stars) - : oldLang(isUnclaimed ? 'BoostingUnclaimedPrize' : 'BoostingCongratulations')} + + {oldLang(isUnclaimed ? 'BoostingUnclaimedPrize' : 'BoostingCongratulations')} - {hasStars ? lang('GiftStarsOutgoing', { - user: ( - - {senderUser && renderText(getSenderTitle(oldLang, senderUser) || '', ['simple_markdown'])} - - ), - }, { - withNodes: true, - }) : targetChat && renderText(oldLang(isFromGiveaway ? 'BoostingReceivedGiftFrom' : isUnclaimed + {targetChat && renderText(oldLang(isFromGiveaway ? 'BoostingReceivedGiftFrom' : isUnclaimed ? 'BoostingReceivedPrizeFrom' : 'BoostingYouHaveUnclaimedPrize', getChatTitle(oldLang, targetChat)), ['simple_markdown'])} - {!hasStars && ( - - {renderText(oldLang( - 'BoostingUnclaimedPrizeDuration', - oldLang('Months', message.content.action?.months, 'i'), - ), ['simple_markdown'])} - - )} + + {renderText(oldLang( + 'BoostingUnclaimedPrizeDuration', + oldLang('Months', message.content.action?.months, 'i'), + ), ['simple_markdown'])} + { - oldLang(hasStars ? 'ActionGiftPremiumView' : 'BoostingReceivedGiftOpenBtn') + oldLang('BoostingReceivedGiftOpenBtn') + } + + + ); + } + + function renderStarsGift() { + return ( + + + + {oldLang('Stars', message.content.action!.stars)} + + + {renderText( + oldLang(!message.isOutgoing + ? 'ActionGiftStarsSubtitleYou' : 'ActionGiftStarsSubtitle', getChatTitle(oldLang, targetChat!)), + ['simple_markdown'], + )} + + { + oldLang('ActionGiftPremiumView') } @@ -323,7 +341,7 @@ const ActionMessage: FC = ({ const className = buildClassName( 'ActionMessage message-list-item', isFocused && !noFocusHighlight && 'focused', - (isGift || isSuggestedAvatar) && 'centered-action', + (isPremiumGift || isSuggestedAvatar) && 'centered-action', isContextMenuShown && 'has-menu-open', isLastInList && 'last-in-list', transitionClassNames, @@ -342,8 +360,9 @@ const ActionMessage: FC = ({ {!isSuggestedAvatar && !isGiftCode && !isJoinedMessage && ( {renderContent()} )} - {isGift && renderGift()} + {isPremiumGift && renderGift()} {isGiftCode && renderGiftCode()} + {isStarsGift && renderStarsGift()} {isSuggestedAvatar && ( )} diff --git a/src/components/modals/ModalContainer.tsx b/src/components/modals/ModalContainer.tsx index 71955d74d..b3d7ad449 100644 --- a/src/components/modals/ModalContainer.tsx +++ b/src/components/modals/ModalContainer.tsx @@ -17,6 +17,7 @@ import OneTimeMediaModal from './oneTimeMedia/OneTimeMediaModal.async'; import ReportAdModal from './reportAd/ReportAdModal.async'; import StarsBalanceModal from './stars/StarsBalanceModal.async'; import StarsPaymentModal from './stars/StarsPaymentModal.async'; +import StarsTransactionInfoModal from './stars/transaction/StarsTransactionModal.async'; import UrlAuthModal from './urlAuth/UrlAuthModal.async'; import WebAppModal from './webApp/WebAppModal.async'; @@ -34,7 +35,8 @@ type ModalKey = keyof Pick; type StateProps = { @@ -63,6 +65,7 @@ const MODALS: ModalRegistry = { mapModal: MapModal, isStarPaymentModalOpen: StarsPaymentModal, starsBalanceModal: StarsBalanceModal, + starsTransactionModal: StarsTransactionInfoModal, }; const MODAL_KEYS = Object.keys(MODALS) as ModalKey[]; const MODAL_ENTRIES = Object.entries(MODALS) as Entries; diff --git a/src/components/modals/stars/StarGiftInfoModal.async.tsx b/src/components/modals/stars/StarGiftInfoModal.async.tsx deleted file mode 100644 index cab07ee85..000000000 --- a/src/components/modals/stars/StarGiftInfoModal.async.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { FC } from '../../../lib/teact/teact'; -import React from '../../../lib/teact/teact'; - -import type { OwnProps } from './StarGiftInfoModal'; - -import { Bundles } from '../../../util/moduleLoader'; - -import useModuleLoader from '../../../hooks/useModuleLoader'; - -const StarGiftInfoModalAsync: FC = (props) => { - const { isOpen } = props; - const StarGiftInfoModal = useModuleLoader(Bundles.Extra, 'StarGiftInfoModal', !isOpen); - - // eslint-disable-next-line react/jsx-props-no-spreading - return StarGiftInfoModal ? : undefined; -}; - -export default StarGiftInfoModalAsync; diff --git a/src/components/modals/stars/StarGiftInfoModal.module.scss b/src/components/modals/stars/StarGiftInfoModal.module.scss deleted file mode 100644 index 625ba7883..000000000 --- a/src/components/modals/stars/StarGiftInfoModal.module.scss +++ /dev/null @@ -1,32 +0,0 @@ -@use '../../../styles/mixins'; - -.centered { - text-align: center !important; -} - -.section { - display: flex; - flex-direction: column; - align-items: center; - - padding: 0.5rem; - position: relative; - - @include mixins.adapt-padding-to-scrollbar(0.5rem); -} - -.info { - display: flex; - justify-content: center; - align-items: center; - margin-bottom: 0.5rem; -} - -.starTitle { - margin: 0; -} - -.footer { - margin: 1rem 0; - color: var(--color-text-secondary); -} diff --git a/src/components/modals/stars/StarGiftInfoModal.tsx b/src/components/modals/stars/StarGiftInfoModal.tsx deleted file mode 100644 index 22f62cd97..000000000 --- a/src/components/modals/stars/StarGiftInfoModal.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import React, { memo, useMemo } from '../../../lib/teact/teact'; -import { getActions, withGlobal } from '../../../global'; - -import type { ApiPeer } from '../../../api/types'; - -import { getSenderTitle } from '../../../global/helpers'; -import { selectTabState, selectUser } from '../../../global/selectors'; -import buildClassName from '../../../util/buildClassName'; -import { formatDateTimeToString } from '../../../util/dates/dateFormat'; -import renderText from '../../common/helpers/renderText'; - -import useLang from '../../../hooks/useLang'; -import useLastCallback from '../../../hooks/useLastCallback'; -import useOldLang from '../../../hooks/useOldLang'; - -import StarIcon from '../../common/icons/StarIcon'; -import SafeLink from '../../common/SafeLink'; -import TableInfoModal, { type TableData } from '../common/TableInfoModal'; - -import styles from './StarGiftInfoModal.module.scss'; - -import StarLogo from '../../../assets/icons/StarLogo.svg'; -import StarsBackground from '../../../assets/stars-bg.png'; - -export type OwnProps = { - isOpen?: boolean; -}; - -export type StateProps = { - stars?: number; - user?: ApiPeer; - date?: number; -}; - -const StarGiftInfoModal = ({ - isOpen, - stars, - user, - date, -}: OwnProps & StateProps) => { - const { - closeStarGiftInfoModal, - } = getActions(); - const oldLang = useOldLang(); - const lang = useLang(); - - const infoText = useMemo(() => { - const linkText = oldLang('GiftStarsSubtitleLinkName'); - - return lang('CreditsBoxHistoryEntryGiftOutAbout', - { - user: ( - - {user && renderText(getSenderTitle(oldLang, user) || '', ['simple_markdown'])} - - ), - link: ( - - ), - }, - { - withNodes: true, - }); - }, [lang, oldLang, user]); - - const footerText = useMemo(() => { - const linkText = oldLang('lng_payments_terms_link'); - return lang('CreditsBoxOutAbout', { - link: ( - - ), - }, { - withNodes: true, - }); - }, [lang, oldLang]); - - const handleButtonClick = useLastCallback(() => { - closeStarGiftInfoModal(); - }); - - const modalData = useMemo(() => { - if (!isOpen) return undefined; - - const header = ( - <> -

{oldLang('StarsGiftSent')}

-
-

{stars}

- -
-

{infoText}

- - ); - - const tableData = [ - [oldLang('Recipient'), user ? { chatId: user.id } : oldLang('BoostingNoRecipient')], - [oldLang('BoostingDate'), formatDateTimeToString(date! * 1000, lang.code, true)], - ] satisfies TableData; - - const footer = ( - - {footerText} - - ); - - return { - header, - tableData, - footer, - }; - }, [isOpen, oldLang, stars, infoText, user, date, lang.code, footerText]); - - if (!modalData) return undefined; - - return ( - - ); -}; - -export default memo(withGlobal( - (global): StateProps => { - const { - starGiftInfoModal, - } = selectTabState(global); - const toUserId = starGiftInfoModal?.toUserId; - const user = toUserId ? selectUser(global, toUserId) : undefined; - - return { - stars: starGiftInfoModal?.stars, - user, - date: starGiftInfoModal?.date, - }; - }, -)(StarGiftInfoModal)); diff --git a/src/components/modals/stars/StarsBalanceModal.tsx b/src/components/modals/stars/StarsBalanceModal.tsx index f46081e4b..7240228b8 100644 --- a/src/components/modals/stars/StarsBalanceModal.tsx +++ b/src/components/modals/stars/StarsBalanceModal.tsx @@ -24,8 +24,8 @@ import Modal from '../../ui/Modal'; import TabList, { type TabWithProperties } from '../../ui/TabList'; import Transition from '../../ui/Transition'; import BalanceBlock from './BalanceBlock'; -import TransactionItem from './StarsTransactionItem'; import StarTopupOptionList from './StarTopupOptionList'; +import TransactionItem from './transaction/StarsTransactionItem'; import styles from './StarsBalanceModal.module.scss'; diff --git a/src/components/modals/stars/StarsPaymentModal.tsx b/src/components/modals/stars/StarsPaymentModal.tsx index 4cd7c3913..b3ac71577 100644 --- a/src/components/modals/stars/StarsPaymentModal.tsx +++ b/src/components/modals/stars/StarsPaymentModal.tsx @@ -22,7 +22,7 @@ import StarIcon from '../../common/icons/StarIcon'; import Button from '../../ui/Button'; import Modal from '../../ui/Modal'; import BalanceBlock from './BalanceBlock'; -import PaidMediaThumb from './PaidMediaThumb'; +import PaidMediaThumb from './transaction/PaidMediaThumb'; import styles from './StarsBalanceModal.module.scss'; diff --git a/src/components/modals/stars/PaidMediaThumb.module.scss b/src/components/modals/stars/transaction/PaidMediaThumb.module.scss similarity index 100% rename from src/components/modals/stars/PaidMediaThumb.module.scss rename to src/components/modals/stars/transaction/PaidMediaThumb.module.scss diff --git a/src/components/modals/stars/PaidMediaThumb.tsx b/src/components/modals/stars/transaction/PaidMediaThumb.tsx similarity index 85% rename from src/components/modals/stars/PaidMediaThumb.tsx rename to src/components/modals/stars/transaction/PaidMediaThumb.tsx index f75ee9d6f..9620219ec 100644 --- a/src/components/modals/stars/PaidMediaThumb.tsx +++ b/src/components/modals/stars/transaction/PaidMediaThumb.tsx @@ -1,14 +1,14 @@ -import React, { memo } from '../../../lib/teact/teact'; +import React, { memo } from '../../../../lib/teact/teact'; -import type { ApiMediaExtendedPreview, BoughtPaidMedia } from '../../../api/types'; +import type { ApiMediaExtendedPreview, BoughtPaidMedia } from '../../../../api/types'; -import { getMediaHash, getMediaThumbUri } from '../../../global/helpers'; -import buildClassName from '../../../util/buildClassName'; +import { getMediaHash, getMediaThumbUri } from '../../../../global/helpers'; +import buildClassName from '../../../../util/buildClassName'; -import useMedia from '../../../hooks/useMedia'; +import useMedia from '../../../../hooks/useMedia'; -import Icon from '../../common/icons/Icon'; -import MediaSpoiler from '../../common/MediaSpoiler'; +import Icon from '../../../common/icons/Icon'; +import MediaSpoiler from '../../../common/MediaSpoiler'; import styles from './PaidMediaThumb.module.scss'; diff --git a/src/components/modals/stars/StarsTransactionItem.module.scss b/src/components/modals/stars/transaction/StarsTransactionItem.module.scss similarity index 100% rename from src/components/modals/stars/StarsTransactionItem.module.scss rename to src/components/modals/stars/transaction/StarsTransactionItem.module.scss diff --git a/src/components/modals/stars/StarsTransactionItem.tsx b/src/components/modals/stars/transaction/StarsTransactionItem.tsx similarity index 72% rename from src/components/modals/stars/StarsTransactionItem.tsx rename to src/components/modals/stars/transaction/StarsTransactionItem.tsx index 51ae77018..10176ad09 100644 --- a/src/components/modals/stars/StarsTransactionItem.tsx +++ b/src/components/modals/stars/transaction/StarsTransactionItem.tsx @@ -1,26 +1,26 @@ -import React, { memo, useMemo } from '../../../lib/teact/teact'; -import { getActions } from '../../../global'; +import React, { memo, useMemo } from '../../../../lib/teact/teact'; +import { getActions } from '../../../../global'; import type { ApiPeer, ApiStarsTransaction, -} from '../../../api/types'; -import type { GlobalState } from '../../../global/types'; -import type { CustomPeer } from '../../../types'; +} from '../../../../api/types'; +import type { GlobalState } from '../../../../global/types'; +import type { CustomPeer } from '../../../../types'; -import { getSenderTitle } from '../../../global/helpers'; -import { buildStarsTransactionCustomPeer, formatStarsTransactionAmount } from '../../../global/helpers/payments'; -import { selectPeer } from '../../../global/selectors'; -import buildClassName from '../../../util/buildClassName'; -import { formatDateTimeToString } from '../../../util/dates/dateFormat'; -import { CUSTOM_PEER_PREMIUM } from '../../../util/objects/customPeer'; +import { getSenderTitle } from '../../../../global/helpers'; +import { buildStarsTransactionCustomPeer, formatStarsTransactionAmount } from '../../../../global/helpers/payments'; +import { selectPeer } from '../../../../global/selectors'; +import buildClassName from '../../../../util/buildClassName'; +import { formatDateTimeToString } from '../../../../util/dates/dateFormat'; +import { CUSTOM_PEER_PREMIUM } from '../../../../util/objects/customPeer'; -import useSelector from '../../../hooks/data/useSelector'; -import useLastCallback from '../../../hooks/useLastCallback'; -import useOldLang from '../../../hooks/useOldLang'; +import useSelector from '../../../../hooks/data/useSelector'; +import useLastCallback from '../../../../hooks/useLastCallback'; +import useOldLang from '../../../../hooks/useOldLang'; -import Avatar from '../../common/Avatar'; -import StarIcon from '../../common/icons/StarIcon'; +import Avatar from '../../../common/Avatar'; +import StarIcon from '../../../common/icons/StarIcon'; import PaidMediaThumb from './PaidMediaThumb'; import styles from './StarsTransactionItem.module.scss'; @@ -36,7 +36,7 @@ function selectOptionalPeer(peerId?: string) { } const StarsTransactionItem = ({ transaction }: OwnProps) => { - const { getStarsReceipt } = getActions(); + const { openStarsTransactionModal } = getActions(); const { date, stars, @@ -90,7 +90,7 @@ const StarsTransactionItem = ({ transaction }: OwnProps) => { }, [lang, peer, transaction]); const handleClick = useLastCallback(() => { - getStarsReceipt({ transaction }); + openStarsTransactionModal({ transaction }); }); return ( diff --git a/src/components/modals/stars/transaction/StarsTransactionModal.async.tsx b/src/components/modals/stars/transaction/StarsTransactionModal.async.tsx new file mode 100644 index 000000000..9aeeafca7 --- /dev/null +++ b/src/components/modals/stars/transaction/StarsTransactionModal.async.tsx @@ -0,0 +1,18 @@ +import type { FC } from '../../../../lib/teact/teact'; +import React from '../../../../lib/teact/teact'; + +import type { OwnProps } from './StarsTransactionModal'; + +import { Bundles } from '../../../../util/moduleLoader'; + +import useModuleLoader from '../../../../hooks/useModuleLoader'; + +const StarsTransactionModalAsync: FC = (props) => { + const { modal } = props; + const StarsTransactionModal = useModuleLoader(Bundles.Extra, 'StarsTransactionInfoModal', !modal); + + // eslint-disable-next-line react/jsx-props-no-spreading + return StarsTransactionModal ? : undefined; +}; + +export default StarsTransactionModalAsync; diff --git a/src/components/modals/stars/transaction/StarsTransactionModal.module.scss b/src/components/modals/stars/transaction/StarsTransactionModal.module.scss new file mode 100644 index 000000000..deb482d33 --- /dev/null +++ b/src/components/modals/stars/transaction/StarsTransactionModal.module.scss @@ -0,0 +1,69 @@ +.modal { + z-index: calc(var(--z-media-viewer) - 1); +} + +.positive { + color: var(--color-success); +} + +.negative { + color: var(--color-error); +} + +.header { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + margin-bottom: 1rem; + position: relative; +} + +.amount { + display: flex; + gap: 0.25rem; + font-size: 1.25rem; + font-weight: 500; + line-height: 1.325; +} + +.title, .description, .amount { + margin-bottom: 0; +} + +.tid { + font-family: var(--font-family-monospace); + font-size: 0.875rem; + cursor: pointer; +} + +.description { + text-align: center; +} + +.footer { + text-align: center; + margin-block: 0.5rem; +} + +.starsBackground { + position: absolute; + height: 8rem; + top: -8.5rem; + left: 50%; + transform: translateX(-50%); +} + +.mediaShift { + top: -1.5rem; +} + +.copyIcon { + margin-inline-start: 0.25rem; + color: var(--color-primary); +} + +.mediaPreview { + margin-bottom: 2rem; + cursor: var(--custom-cursor, pointer); +} diff --git a/src/components/modals/stars/transaction/StarsTransactionModal.tsx b/src/components/modals/stars/transaction/StarsTransactionModal.tsx new file mode 100644 index 000000000..a63c75e26 --- /dev/null +++ b/src/components/modals/stars/transaction/StarsTransactionModal.tsx @@ -0,0 +1,213 @@ +import type { FC } from '../../../../lib/teact/teact'; +import React, { memo, useMemo } from '../../../../lib/teact/teact'; +import { getActions, withGlobal } from '../../../../global'; + +import type { + ApiPeer, + ApiStarsTransactionPeer, +} from '../../../../api/types'; +import type { TabState } from '../../../../global/types'; +import { MediaViewerOrigin } from '../../../../types'; + +import { getMessageLink } from '../../../../global/helpers'; +import { buildStarsTransactionCustomPeer, formatStarsTransactionAmount } from '../../../../global/helpers/payments'; +import { selectPeer } from '../../../../global/selectors'; +import buildClassName from '../../../../util/buildClassName'; +import { copyTextToClipboard } from '../../../../util/clipboard'; +import { formatDateTimeToString } from '../../../../util/dates/dateFormat'; + +import useLastCallback from '../../../../hooks/useLastCallback'; +import useOldLang from '../../../../hooks/useOldLang'; +import usePrevious from '../../../../hooks/usePrevious'; + +import Icon from '../../../common/icons/Icon'; +import StarIcon from '../../../common/icons/StarIcon'; +import SafeLink from '../../../common/SafeLink'; +import TableInfoModal, { type TableData } from '../../common/TableInfoModal'; +import PaidMediaThumb from './PaidMediaThumb'; + +import styles from './StarsTransactionModal.module.scss'; + +import StarsBackground from '../../../../assets/stars-bg.png'; + +export type OwnProps = { + modal: TabState['starsTransactionModal']; +}; + +type StateProps = { + peer?: ApiPeer; +}; + +const StarsTransactionModal: FC = ({ + modal, peer, +}) => { + const { showNotification, openMediaViewer, closeStarsTransactionModal } = getActions(); + const lang = useOldLang(); + const { transaction } = modal || {}; + + const handleOpenMedia = useLastCallback(() => { + const media = transaction?.extendedMedia; + if (!media) return; + + openMediaViewer({ + origin: MediaViewerOrigin.StarsTransaction, + standaloneMedia: media.flatMap((item) => Object.values(item)), + }); + }); + + const starModalData = useMemo(() => { + if (!transaction) { + return undefined; + } + + const customPeer = (transaction.peer && transaction.peer.type !== 'peer' + && buildStarsTransactionCustomPeer(transaction.peer)) || undefined; + + const peerId = transaction.peer?.type === 'peer' ? transaction.peer.id : undefined; + const toName = transaction.peer && lang(getStarsPeerTitleKey(transaction.peer)); + + const title = transaction.title || (customPeer ? lang(customPeer.titleKey) : undefined); + + const messageLink = peer && transaction.messageId + ? getMessageLink(peer, undefined, transaction.messageId) : undefined; + + const media = transaction.extendedMedia; + + const mediaAmount = media?.length || 0; + const areAllPhotos = media?.every((m) => !m.video); + const areAllVideos = media?.every((m) => !m.photo); + + const mediaText = areAllPhotos ? lang('Stars.Transfer.Photos', mediaAmount) + : areAllVideos ? lang('Stars.Transfer.Videos', mediaAmount) + : lang('Media', mediaAmount); + + const description = transaction.description || (media ? mediaText : undefined); + + const header = ( +
+ {media && ( + + )} + + {title &&

{title}

} +

{description}

+

+ + {formatStarsTransactionAmount(transaction.stars)} + + +

+
+ ); + + const tableData: TableData = []; + + tableData.push([ + lang(transaction.stars < 0 || transaction.isMyGift ? 'Stars.Transaction.To' + : peerId ? 'Star.Transaction.From' : 'Stars.Transaction.Via'), + peerId ? { chatId: peerId } : toName || '', + ]); + + if (messageLink) { + tableData.push([lang('Stars.Transaction.Media'), ]); + } + + if (transaction.id) { + tableData.push([ + lang('Stars.Transaction.Id'), + ( + { + copyTextToClipboard(transaction.id!); + showNotification({ + message: lang('StarsTransactionIDCopied'), + }); + }} + > + {transaction.id} + + + ), + ]); + } + + tableData.push([ + lang('Stars.Transaction.Date'), + formatDateTimeToString(transaction.date * 1000, lang.code, true), + ]); + + const footerText = lang('lng_credits_box_out_about'); + const footerTextParts = footerText.split('{link}'); + + const footer = ( + + {footerTextParts[0]} + + {footerTextParts[1]} + + ); + + return { + header, + tableData, + footer, + avatarPeer: !transaction.photo ? (peer || customPeer) : undefined, + }; + }, [lang, transaction, peer]); + + const prevModalData = usePrevious(starModalData); + const renderingModalData = prevModalData || starModalData; + + return ( + + ); +}; + +export default memo(withGlobal( + (global, { modal }): StateProps => { + const peerId = modal?.transaction?.peer?.type === 'peer' && modal.transaction.peer.id; + const peer = peerId ? selectPeer(global, peerId) : undefined; + + return { + peer, + }; + }, +)(StarsTransactionModal)); + +function getStarsPeerTitleKey(peer: ApiStarsTransactionPeer) { + switch (peer.type) { + case 'appStore': + return 'AppStore'; + case 'playMarket': + return 'PlayMarket'; + case 'fragment': + return 'Fragment'; + case 'premiumBot': + return 'StarsTransactionBot'; + case 'ads': + return 'StarsTransactionAds'; + default: + return 'Stars.Transaction.Unsupported.Title'; + } +} diff --git a/src/components/payment/ReceiptModal.tsx b/src/components/payment/ReceiptModal.tsx index da01d30ef..3bd7315b9 100644 --- a/src/components/payment/ReceiptModal.tsx +++ b/src/components/payment/ReceiptModal.tsx @@ -1,211 +1,60 @@ import type { FC } from '../../lib/teact/teact'; import React, { memo, useEffect, useMemo } from '../../lib/teact/teact'; -import { getActions, withGlobal } from '../../global'; +import { withGlobal } from '../../global'; -import type { - ApiInvoice, - ApiPeer, - ApiReceipt, ApiReceiptRegular, ApiShippingAddress, - ApiStarsTransactionPeer, -} from '../../api/types'; -import { MediaViewerOrigin } from '../../types'; +import type { ApiInvoice, ApiShippingAddress, ApiWebDocument } from '../../api/types'; +import type { Price } from '../../types'; -import { getMessageLink } from '../../global/helpers'; -import { buildStarsTransactionCustomPeer, formatStarsTransactionAmount } from '../../global/helpers/payments'; -import { selectPeer, selectTabState } from '../../global/selectors'; -import buildClassName from '../../util/buildClassName'; -import { copyTextToClipboard } from '../../util/clipboard'; -import { formatDateTimeToString } from '../../util/dates/dateFormat'; +import { selectTabState } from '../../global/selectors'; import useFlag from '../../hooks/useFlag'; -import useLastCallback from '../../hooks/useLastCallback'; -import useOldLang from '../../hooks/useOldLang'; +import useLang from '../../hooks/useLang'; -import Icon from '../common/icons/Icon'; -import StarIcon from '../common/icons/StarIcon'; -import SafeLink from '../common/SafeLink'; -import TableInfoModal, { type TableData } from '../modals/common/TableInfoModal'; -import PaidMediaThumb from '../modals/stars/PaidMediaThumb'; import Button from '../ui/Button'; import Modal from '../ui/Modal'; import Checkout from './Checkout'; import './PaymentModal.scss'; -import styles from './ReceiptModal.module.scss'; - -import StarsBackground from '../../assets/stars-bg.png'; export type OwnProps = { isOpen?: boolean; - onClose: NoneToVoidFunction; + onClose: () => void; }; type StateProps = { - receipt?: ApiReceipt; - peer?: ApiPeer; + prices?: Price[]; + shippingPrices: any; + tipAmount?: number; + totalAmount?: number; + currency?: string; + info?: { + shippingAddress?: ApiShippingAddress; + phone?: string; + name?: string; + }; + photo?: ApiWebDocument; + text?: string; + title?: string; + credentialsTitle?: string; + shippingMethod?: string; }; const ReceiptModal: FC = ({ - isOpen, receipt, peer, onClose, + isOpen, + onClose, + prices, + shippingPrices, + tipAmount, + totalAmount, + currency, + info, + photo, + text, + title, + credentialsTitle, + shippingMethod, }) => { - const { showNotification, openMediaViewer } = getActions(); - const lang = useOldLang(); - - const handleOpenMedia = useLastCallback(() => { - const media = receipt?.type === 'stars' && receipt.media; - if (!media) return; - - openMediaViewer({ - origin: MediaViewerOrigin.StarsTransaction, - standaloneMedia: media.flatMap((item) => Object.values(item)), - }); - }); - - const starModalData = useMemo(() => { - if (receipt?.type !== 'stars') return undefined; - - const customPeer = (receipt.peer && receipt.peer.type !== 'peer' && buildStarsTransactionCustomPeer(receipt.peer)) - || undefined; - - const botId = receipt.botId || (receipt.peer?.type === 'peer' ? receipt.peer.id : undefined); - const toName = receipt.peer && lang(getStarsPeerTitleKey(receipt.peer)); - - const title = receipt.title || (customPeer ? lang(customPeer.titleKey) : undefined); - - const messageLink = peer && receipt.messageId ? getMessageLink(peer, undefined, receipt.messageId) : undefined; - - const media = receipt.media; - - const mediaAmount = media?.length || 0; - const areAllPhotos = media?.every((m) => !m.video); - const areAllVideos = media?.every((m) => !m.photo); - - const mediaText = areAllPhotos ? lang('Stars.Transfer.Photos', mediaAmount) - : areAllVideos ? lang('Stars.Transfer.Videos', mediaAmount) - : lang('Media', mediaAmount); - - const description = receipt.text || (media ? mediaText : undefined); - - const header = ( -
- {media && ( - - )} - - {title &&

{title}

} -

{description}

-

- - {formatStarsTransactionAmount(receipt.totalAmount)} - - -

-
- ); - - const tableData: TableData = []; - - tableData.push([ - lang(receipt.totalAmount < 0 ? 'Stars.Transaction.To' : 'Stars.Transaction.Via'), - botId ? { chatId: botId } : toName || '', - ]); - - if (messageLink) { - tableData.push([lang('Stars.Transaction.Media'), ]); - } - - tableData.push([ - lang('Stars.Transaction.Id'), - ( - { - copyTextToClipboard(receipt.transactionId); - showNotification({ - message: lang('StarsTransactionIDCopied'), - }); - }} - > - {receipt.transactionId} - - - ), - ]); - - tableData.push([ - lang('Stars.Transaction.Date'), - formatDateTimeToString(receipt.date * 1000, lang.code, true), - ]); - - const footerText = lang('lng_credits_box_out_about'); - const footerTextParts = footerText.split('{link}'); - - const footer = ( - - {footerTextParts[0]} - - {footerTextParts[1]} - - ); - - return { - header, - tableData, - footer, - avatarPeer: !receipt.photo ? (peer || customPeer) : undefined, - }; - }, [lang, receipt, peer]); - - if (receipt?.type === 'regular') { - return ; - } - - return ( - - ); -}; - -function ReceiptModalRegular({ - isOpen, receipt, onClose, -}: { - isOpen?: boolean; - receipt: ApiReceiptRegular; - onClose: NoneToVoidFunction; -}) { - const { - credentialsTitle, - currency, - prices, - tipAmount, - totalAmount, - info, - photo, - shippingMethod, - shippingPrices, - text, - title, - } = receipt; - const lang = useOldLang(); + const lang = useLang(); const [isModalOpen, openModal, closeModal] = useFlag(); @@ -265,18 +114,37 @@ function ReceiptModalRegular({ ); -} +}; export default memo(withGlobal( (global): StateProps => { const { receipt } = selectTabState(global).payment; - - const peerId = receipt?.type === 'stars' && (receipt.botId || (receipt.peer?.type === 'peer' && receipt.peer.id)); - const peer = peerId ? selectPeer(global, peerId) : undefined; + const { + currency, + prices, + info, + totalAmount, + credentialsTitle, + shippingPrices, + shippingMethod, + photo, + text, + title, + tipAmount, + } = (receipt || {}); return { - receipt, - peer, + currency, + prices, + info, + tipAmount, + totalAmount, + credentialsTitle, + shippingPrices, + shippingMethod, + photo, + text, + title, }; }, )(ReceiptModal)); @@ -304,20 +172,3 @@ function getCheckoutInfo(paymentMethod?: string, shippingMethod, }; } - -function getStarsPeerTitleKey(peer: ApiStarsTransactionPeer) { - switch (peer.type) { - case 'appStore': - return 'AppStore'; - case 'playMarket': - return 'PlayMarket'; - case 'fragment': - return 'Fragment'; - case 'premiumBot': - return 'StarsTransactionBot'; - case 'ads': - return 'StarsTransactionAds'; - default: - return 'Stars.Transaction.Unsupported.Title'; - } -} diff --git a/src/global/actions/api/payments.ts b/src/global/actions/api/payments.ts index bca39b66a..2967beb95 100644 --- a/src/global/actions/api/payments.ts +++ b/src/global/actions/api/payments.ts @@ -12,11 +12,13 @@ import { buildQueryString } from '../../../util/requestQuery'; import { extractCurrentThemeParams } from '../../../util/themeStyle'; import { callApi } from '../../../api/gramjs'; import { isChatChannel, isChatSuperGroup } from '../../helpers'; -import { getRequestInputInvoice } from '../../helpers/payments'; +import { getRequestInputInvoice, getStarsTransactionFromGift } from '../../helpers/payments'; import { addActionHandler, getGlobal, setGlobal } from '../../index'; import { addChats, addUsers, appendStarsTransactions, closeInvoice, + openStarsTransactionFromReceipt, + openStarsTransactionModal, setInvoiceInfo, setPaymentForm, setPaymentStep, setReceipt, @@ -24,7 +26,6 @@ import { setSmartGlocalCardInfo, setStripeCardInfo, updateChatFullInfo, updatePayment, - updateReceiptFromStarsTransaction, updateShippingOptions, updateStarsBalance, } from '../../reducers'; @@ -32,6 +33,7 @@ import { updateTabState } from '../../reducers/tabs'; import { selectChat, selectChatFullInfo, + selectChatMessage, selectPaymentFormId, selectPaymentInputInvoice, selectPaymentRequestId, selectProviderPublicToken, @@ -132,15 +134,14 @@ addActionHandler('getReceipt', async (global, actions, payload): Promise = global = getGlobal(); global = addUsers(global, buildCollectionByKey(result.users, 'id')); - global = setReceipt(global, result.receipt, tabId); + if (result.receipt.type === 'stars') { + global = openStarsTransactionFromReceipt(global, result.receipt, tabId); + } else { + global = setReceipt(global, result.receipt, tabId); + } setGlobal(global); }); -addActionHandler('getStarsReceipt', (global, actions, payload): ActionReturnType => { - const { transaction, tabId = getCurrentTabId() } = payload; - return updateReceiptFromStarsTransaction(global, transaction, tabId); -}); - addActionHandler('clearPaymentError', (global, actions, payload): ActionReturnType => { const { tabId = getCurrentTabId() } = payload || {}; global = updateTabState(global, { @@ -534,37 +535,20 @@ addActionHandler('closeStarsGiftingModal', (global, actions, payload): ActionRet }, tabId); }); -addActionHandler('openStarGiftInfoModal', (global, actions, payload): ActionReturnType => { +addActionHandler('openStarsTransactionFromGift', (global, actions, payload): ActionReturnType => { const { - toUserId, - stars, - date, + chatId, + messageId, tabId = getCurrentTabId(), } = payload || {}; - if (!stars || !toUserId || !date) { - return; - } + const message = selectChatMessage(global, chatId, messageId); + if (!message) return undefined; - global = getGlobal(); + const transaction = getStarsTransactionFromGift(message); + if (!transaction) return undefined; - global = updateTabState(global, { - starGiftInfoModal: { - toUserId, - stars, - date, - isOpen: true, - }, - }, tabId); - setGlobal(global); -}); - -addActionHandler('closeStarGiftInfoModal', (global, actions, payload): ActionReturnType => { - const { tabId = getCurrentTabId() } = payload || {}; - - return updateTabState(global, { - starGiftInfoModal: undefined, - }, tabId); + return openStarsTransactionModal(global, transaction, tabId); }); addActionHandler('openPremiumGiftModal', async (global, actions, payload): Promise => { diff --git a/src/global/actions/ui/payments.ts b/src/global/actions/ui/payments.ts index 3b19a1b98..d045d1b8e 100644 --- a/src/global/actions/ui/payments.ts +++ b/src/global/actions/ui/payments.ts @@ -2,7 +2,9 @@ import type { ActionReturnType } from '../../types'; import { getCurrentTabId } from '../../../util/establishMultitabRole'; import { addActionHandler } from '../../index'; -import { clearPayment, closeInvoice, updatePayment } from '../../reducers'; +import { + clearPayment, closeInvoice, openStarsTransactionModal, updatePayment, +} from '../../reducers'; import { updateTabState } from '../../reducers/tabs'; import { selectTabState } from '../../selectors'; @@ -72,3 +74,16 @@ addActionHandler('closeStarsBalanceModal', (global, actions, payload): ActionRet starsBalanceModal: undefined, }, tabId); }); + +addActionHandler('openStarsTransactionModal', (global, actions, payload): ActionReturnType => { + const { transaction, tabId = getCurrentTabId() } = payload; + return openStarsTransactionModal(global, transaction, tabId); +}); + +addActionHandler('closeStarsTransactionModal', (global, actions, payload): ActionReturnType => { + const { tabId = getCurrentTabId() } = payload || {}; + + return updateTabState(global, { + starsTransactionModal: undefined, + }, tabId); +}); diff --git a/src/global/helpers/payments.ts b/src/global/helpers/payments.ts index a051ab8b1..cfe0a59f4 100644 --- a/src/global/helpers/payments.ts +++ b/src/global/helpers/payments.ts @@ -1,5 +1,10 @@ import type { - ApiInputInvoice, ApiRequestInputInvoice, ApiStarsTransactionPeer, ApiStarsTransactionPeerPeer, + ApiInputInvoice, + ApiMessage, + ApiRequestInputInvoice, + ApiStarsTransaction, + ApiStarsTransactionPeer, + ApiStarsTransactionPeerPeer, } from '../../api/types'; import type { CustomPeer } from '../../types'; import type { GlobalState } from '../types'; @@ -141,7 +146,7 @@ export function buildStarsTransactionCustomPeer( isCustomPeer: true, titleKey: 'Stars.Intro.Transaction.FragmentTopUp.Title', subtitleKey: 'Stars.Intro.Transaction.FragmentTopUp.Subtitle', - peerColorId: -1, // Defaults to black + customPeerAvatarColor: '#000000', }; } @@ -182,3 +187,23 @@ export function formatStarsTransactionAmount(amount: number) { return `+ ${formatInteger(amount)}`; } + +export function getStarsTransactionFromGift(message: ApiMessage): ApiStarsTransaction | undefined { + const { action } = message.content; + + if (action?.type !== 'giftStars') return undefined; + + const { transactionId, stars } = action; + + return { + id: transactionId!, + stars: stars!, + peer: { + type: 'peer', + id: message.isOutgoing ? message.chatId : message.senderId!, + }, + date: message.date, + isGift: true, + isMyGift: message.isOutgoing || undefined, + }; +} diff --git a/src/global/reducers/payments.ts b/src/global/reducers/payments.ts index b0f94df5a..e2d1b265c 100644 --- a/src/global/reducers/payments.ts +++ b/src/global/reducers/payments.ts @@ -1,5 +1,6 @@ import type { - ApiInvoice, ApiPaymentForm, ApiReceipt, + ApiInvoice, ApiPaymentForm, + ApiReceiptRegular, ApiReceiptStars, ApiStarsTransaction, } from '../../api/types'; @@ -8,7 +9,6 @@ import type { GlobalState, StarsTransactionType, TabArgs, TabState, } from '../types'; -import { STARS_CURRENCY_CODE } from '../../config'; import { getCurrentTabId } from '../../util/establishMultitabRole'; import { selectTabState } from '../selectors'; import { updateTabState } from './tabs'; @@ -113,7 +113,7 @@ export function setConfirmPaymentUrl( export function setReceipt( global: T, - receipt?: ApiReceipt, + receipt?: ApiReceiptRegular, ...[tabId = getCurrentTabId()]: TabArgs ): T { if (!receipt) { @@ -187,22 +187,30 @@ export function appendStarsTransactions( }; } -export function updateReceiptFromStarsTransaction( +export function openStarsTransactionModal( global: T, transaction: ApiStarsTransaction, ...[tabId = getCurrentTabId()]: TabArgs ): T { - const receipt: ApiReceiptStars = { - type: 'stars', - totalAmount: transaction.stars, - currency: STARS_CURRENCY_CODE, - peer: transaction.peer, - date: transaction.date, - text: transaction.description, - title: transaction.title, - transactionId: transaction.id, - photo: transaction.photo, - media: transaction.extendedMedia, - messageId: transaction.messageId, + return updateTabState(global, { + starsTransactionModal: { + transaction, + }, + }, tabId); +} + +export function openStarsTransactionFromReceipt( + global: T, receipt: ApiReceiptStars, ...[tabId = getCurrentTabId()]: TabArgs +): T { + const transaction: ApiStarsTransaction = { + id: receipt.transactionId, + peer: receipt.peer, + stars: receipt.totalAmount, + date: receipt.date, + title: receipt.title, + description: receipt.text, + photo: receipt.photo, + extendedMedia: receipt.media, + messageId: receipt.messageId, }; - return updatePayment(global, { receipt }, tabId); + return openStarsTransactionModal(global, transaction, tabId); } diff --git a/src/global/types.ts b/src/global/types.ts index 25a9dc36c..440552c59 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -56,7 +56,7 @@ import type { ApiQuickReply, ApiReaction, ApiReactionKey, - ApiReceipt, + ApiReceiptRegular, ApiReportReason, ApiSavedReactionTag, ApiSendMessageAction, @@ -547,7 +547,7 @@ export type TabState = { }; passwordMissing?: boolean; savedCredentials?: ApiPaymentCredentials[]; - receipt?: ApiReceipt; + receipt?: ApiReceiptRegular; error?: { field?: string; message?: string; @@ -720,13 +720,6 @@ export type TabState = { isOpen?: boolean; }; - starGiftInfoModal?: { - isOpen?: boolean; - toUserId: string; - date: number; - stars: number; - }; - starsGiftModal?: { isCompleted?: boolean; isOpen?: boolean; @@ -734,6 +727,10 @@ export type TabState = { starsGiftOptions?: ApiStarsGiftOption[]; }; + starsTransactionModal?: { + transaction: ApiStarsTransaction; + }; + giftModal?: { isCompleted?: boolean; isOpen?: boolean; @@ -1770,9 +1767,14 @@ export interface ActionPayloads { chatId: string; messageId: number; } & WithTabId; - getStarsReceipt: { + openStarsTransactionModal: { transaction: ApiStarsTransaction; } & WithTabId; + openStarsTransactionFromGift: { + chatId: string; + messageId: number; + } & WithTabId; + closeStarsTransactionModal: WithTabId | undefined; sendCredentialsInfo: { credentials: ApiCredentials; } & WithTabId; @@ -3241,13 +3243,6 @@ export interface ActionPayloads { } & WithTabId) | undefined; closeStarsGiftModal: WithTabId | undefined; - openStarGiftInfoModal: ({ - toUserId?: string; - stars?: number; - date?: number; - } & WithTabId) | undefined; - closeStarGiftInfoModal: WithTabId | undefined; - setEmojiStatus: { emojiStatus: ApiSticker; expires?: number; diff --git a/src/lib/gramjs/tl/apiTl.js b/src/lib/gramjs/tl/apiTl.js index 56e360334..62dcf76f0 100644 --- a/src/lib/gramjs/tl/apiTl.js +++ b/src/lib/gramjs/tl/apiTl.js @@ -1633,6 +1633,7 @@ payments.getStarsStatus#104fcfa7 peer:InputPeer = payments.StarsStatus; payments.getStarsTransactions#97938d5a flags:# inbound:flags.0?true outbound:flags.1?true ascending:flags.2?true peer:InputPeer offset:string limit:int = payments.StarsStatus; payments.sendStarsForm#2bb731d flags:# form_id:long invoice:InputInvoice = payments.PaymentResult; payments.refundStarsCharge#25ae8f4a user_id:InputUser charge_id:string = Updates; +payments.getStarsTransactionsByID#27842d2e peer:InputPeer id:Vector = payments.StarsStatus; payments.getStarsGiftOptions#d3c96bc8 flags:# user_id:flags.0?InputUser = Vector; phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall; @@ -1698,4 +1699,4 @@ premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset: premium.getMyBoosts#be77b4a = premium.MyBoosts; premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector peer:InputPeer = premium.MyBoosts; premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus; -fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;`; +fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;`; \ No newline at end of file diff --git a/src/lib/gramjs/tl/static/api.json b/src/lib/gramjs/tl/static/api.json index e43f0307a..c6847a07a 100644 --- a/src/lib/gramjs/tl/static/api.json +++ b/src/lib/gramjs/tl/static/api.json @@ -361,6 +361,7 @@ "payments.getStarsTopupOptions", "payments.getStarsStatus", "payments.getStarsTransactions", + "payments.getStarsTransactionsByID", "payments.sendStarsForm", "payments.getStarsGiftOptions", "payments.refundStarsCharge", diff --git a/src/types/index.ts b/src/types/index.ts index 35b840068..d40879240 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -520,6 +520,7 @@ export interface CustomPeer { avatarIcon: IconName; isAvatarSquare?: boolean; peerColorId?: number; + customPeerAvatarColor?: string; withPremiumGradient?: boolean; }