From e1504323f1ba47083b642880740855fe45642c71 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Thu, 29 Aug 2024 15:52:54 +0200 Subject: [PATCH] Stars Gifting: Fixes for Stars Gifting (#4899) --- src/api/gramjs/apiBuilders/payments.ts | 4 +- src/api/types/payments.ts | 7 - src/components/common/Avatar.scss | 11 ++ src/components/common/Avatar.tsx | 3 +- src/components/common/AvatarList.module.scss | 4 + src/components/common/AvatarStoryCircle.tsx | 1 + .../helpers/renderActionMessageText.tsx | 5 +- src/components/common/pickers/PickerModal.tsx | 25 +-- .../premium/PremiumGiftingPickerModal.tsx | 1 + .../main/premium/StarsGiftModal.module.scss | 67 +++++---- .../main/premium/StarsGiftModal.tsx | 82 ++++++---- .../main/premium/StarsGiftingPickerModal.tsx | 64 +++----- src/components/middle/ActionMessage.tsx | 20 ++- src/components/middle/MessageList.scss | 15 +- .../modals/common/TableInfoModal.tsx | 4 +- .../stars/StarTopupOptionList.module.scss | 13 ++ .../modals/stars/StarTopupOptionList.tsx | 4 +- .../stars/StarsBalanceModal.module.scss | 15 +- .../modals/stars/StarsBalanceModal.tsx | 64 ++++---- .../StarsTransactionModal.module.scss | 20 ++- .../transaction/StarsTransactionModal.tsx | 142 ++++++++++++++---- src/global/actions/api/payments.ts | 3 +- src/global/actions/apiUpdaters/payments.ts | 17 +++ src/global/selectors/symbols.ts | 23 +++ src/global/types.ts | 6 +- 25 files changed, 406 insertions(+), 214 deletions(-) diff --git a/src/api/gramjs/apiBuilders/payments.ts b/src/api/gramjs/apiBuilders/payments.ts index 3d48fb0c3..2ba4608ee 100644 --- a/src/api/gramjs/apiBuilders/payments.ts +++ b/src/api/gramjs/apiBuilders/payments.ts @@ -8,7 +8,7 @@ import type { ApiGiveawayInfo, ApiInvoice, ApiLabeledPrice, ApiMyBoost, ApiPaymentCredentials, ApiPaymentForm, ApiPaymentSavedInfo, ApiPremiumGiftCodeOption, ApiPremiumPromo, ApiPremiumSubscriptionOption, - ApiReceipt, ApiStarsGiftOption, + ApiReceipt, ApiStarsTransaction, ApiStarsTransactionPeer, ApiStarTopupOption, @@ -386,7 +386,7 @@ export function buildApiPremiumGiftCodeOption(option: GramJs.PremiumGiftCodeOpti }; } -export function buildApiStarsGiftOptions(option: GramJs.StarsGiftOption): ApiStarsGiftOption { +export function buildApiStarsGiftOptions(option: GramJs.StarsGiftOption): ApiStarTopupOption { const { extended, stars, amount, currency, } = option; diff --git a/src/api/types/payments.ts b/src/api/types/payments.ts index 7c8184239..d13e82c71 100644 --- a/src/api/types/payments.ts +++ b/src/api/types/payments.ts @@ -159,13 +159,6 @@ export interface ApiPremiumGiftCodeOption { amount: number; } -export interface ApiStarsGiftOption { - isExtended?: true; - stars: number; - currency: string; - amount: number; -} - export type ApiBoostsStatus = { level: number; currentLevelBoosts: number; diff --git a/src/components/common/Avatar.scss b/src/components/common/Avatar.scss index 55b5740ec..1846cb28e 100644 --- a/src/components/common/Avatar.scss +++ b/src/components/common/Avatar.scss @@ -135,6 +135,17 @@ } } + &.size-huge { + width: 6rem; + height: 6rem; + font-size: 3rem; + + .emoji { + width: 3rem; + height: 3rem; + } + } + &.size-jumbo { width: 7.5rem; height: 7.5rem; diff --git a/src/components/common/Avatar.tsx b/src/components/common/Avatar.tsx index 5a02fefb8..00bc0762e 100644 --- a/src/components/common/Avatar.tsx +++ b/src/components/common/Avatar.tsx @@ -45,7 +45,8 @@ import './Avatar.scss'; const LOOP_COUNT = 3; -export type AvatarSize = 'micro' | 'tiny' | 'mini' | 'small' | 'small-mobile' | 'medium' | 'large' | 'giant' | 'jumbo'; +export type AvatarSize = + 'micro' | 'tiny' | 'mini' | 'small' | 'small-mobile' | 'medium' | 'large' | 'giant' | 'huge' | 'jumbo'; const cn = createClassNameBuilder('Avatar'); cn.media = cn('media'); diff --git a/src/components/common/AvatarList.module.scss b/src/components/common/AvatarList.module.scss index 76e061bea..d0c310051 100644 --- a/src/components/common/AvatarList.module.scss +++ b/src/components/common/AvatarList.module.scss @@ -36,6 +36,10 @@ --size: 3.375rem; } +.size-huge { + --size: 6.5rem; +} + .size-jumbo { --size: 7.5rem; } diff --git a/src/components/common/AvatarStoryCircle.tsx b/src/components/common/AvatarStoryCircle.tsx index 714e452bf..f94d03fff 100644 --- a/src/components/common/AvatarStoryCircle.tsx +++ b/src/components/common/AvatarStoryCircle.tsx @@ -37,6 +37,7 @@ const SIZES: Record = { medium: 2.875 * REM, large: 3.5 * REM, giant: 5.125 * REM, + huge: 6.125 * REM, jumbo: 7.625 * REM, }; diff --git a/src/components/common/helpers/renderActionMessageText.tsx b/src/components/common/helpers/renderActionMessageText.tsx index bde0cf1b4..5b22926d9 100644 --- a/src/components/common/helpers/renderActionMessageText.tsx +++ b/src/components/common/helpers/renderActionMessageText.tsx @@ -7,6 +7,7 @@ import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import type { LangFn } from '../../../hooks/useOldLang'; import type { TextPart } from '../../../types'; +import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config'; import { getChatTitle, getExpiredMessageDescription, @@ -111,7 +112,9 @@ export function renderActionMessageText( actionOriginChat ? ( renderChatContent(lang, actionOriginChat, noLinks) || NBSP ) : actionOriginUser ? ( - renderUserContent(actionOriginUser, noLinks) || NBSP + actionOriginUser.id === SERVICE_NOTIFICATIONS_USER_ID + ? lang('StarsTransactionUnknown') + : renderUserContent(actionOriginUser, noLinks) || NBSP ) : 'User', '', ); diff --git a/src/components/common/pickers/PickerModal.tsx b/src/components/common/pickers/PickerModal.tsx index a02f06423..ca20cc653 100644 --- a/src/components/common/pickers/PickerModal.tsx +++ b/src/components/common/pickers/PickerModal.tsx @@ -28,6 +28,7 @@ const PickerModal = ({ ...modalProps }: OwnProps) => { const lang = useOldLang(); + const hasOnClickHandler = Boolean(onConfirm || modalProps.onClose); return ( {modalProps.children} -
- -
+ {hasOnClickHandler && ( +
+ +
+ )}
); }; diff --git a/src/components/main/premium/PremiumGiftingPickerModal.tsx b/src/components/main/premium/PremiumGiftingPickerModal.tsx index 3f0fb7ac3..113372b2b 100644 --- a/src/components/main/premium/PremiumGiftingPickerModal.tsx +++ b/src/components/main/premium/PremiumGiftingPickerModal.tsx @@ -86,6 +86,7 @@ const PremiumGiftingPickerModal: FC = ({ onConfirm={handleSendIdList} onEnter={handleSendIdList} withPremiumGradient + isConfirmDisabled={!selectedUserIds?.length} > = ({ const handleClick = useLastCallback((option: ApiStarTopupOption) => { setSelectedOption(option); - openInvoice({ - type: 'starsgift', - userId: forUserId!, - stars: option.stars, - currency: option.currency, - amount: option.amount, - }); + if (user) { + openInvoice({ + type: 'starsgift', + userId: forUserId!, + stars: option.stars, + currency: option.currency, + amount: option.amount, + }); + } else { + openInvoice({ + type: 'stars', + stars: option.stars, + currency: option.currency, + amount: option.amount, + }); + } }); function handleScroll(e: React.UIEvent) { @@ -105,11 +118,12 @@ const StarsGiftModal: FC = ({ function renderGiftTitle() { if (isCompleted) { - return renderText(oldLang('Notification.StarsGift.SentYou', - formatCurrencyAsString(selectedOption!.amount, selectedOption!.currency, oldLang.code)), ['simple_markdown']); + return user ? renderText(oldLang('Notification.StarsGift.SentYou', + formatCurrencyAsString(selectedOption!.amount, selectedOption!.currency, oldLang.code)), ['simple_markdown']) + : renderText(oldLang('StarsAcquiredInfo', selectedOption?.stars), ['simple_markdown']); } - return oldLang('GiftStarsTitle'); + return user ? oldLang('GiftStarsTitle') : oldLang('Star.List.GetStars'); } function renderStarOptionList() { @@ -154,28 +168,40 @@ const StarsGiftModal: FC = ({

- {oldLang('GiftStarsTitle')} + {user ? oldLang('GiftStarsTitle') : oldLang('Star.List.GetStars')}

-
- +
+ {user ? ( + <> + + + + ) : ( + <> + + + + )}

{renderGiftTitle()}

- {!isCompleted && ( - <> -
- {renderStarOptionList()} -
-
- {bottomText} -
- - )} +

+ {user ? renderText( + oldLang('ActionGiftStarsSubtitle', getSenderTitle(oldLang, user)), ['simple_markdown'], + ) : oldLang('Stars.Purchase.GetStarsInfo')} +

+
+ {renderStarOptionList()} +
+ {bottomText} +
+
); diff --git a/src/components/main/premium/StarsGiftingPickerModal.tsx b/src/components/main/premium/StarsGiftingPickerModal.tsx index d9d0477f8..1f5cb4040 100644 --- a/src/components/main/premium/StarsGiftingPickerModal.tsx +++ b/src/components/main/premium/StarsGiftingPickerModal.tsx @@ -1,7 +1,6 @@ import type { FC } from '../../../lib/teact/teact'; import React, { memo, useMemo, - useRef, useState, } from '../../../lib/teact/teact'; import { getActions, getGlobal, withGlobal } from '../../../global'; @@ -10,17 +9,14 @@ import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config'; import { filterUsersByName, isDeletedUser, isUserBot, } from '../../../global/helpers'; -import buildClassName from '../../../util/buildClassName'; import { unique } from '../../../util/iteratees'; import sortChatIds from '../../common/helpers/sortChatIds'; import useLastCallback from '../../../hooks/useLastCallback'; import useOldLang from '../../../hooks/useOldLang'; -import Icon from '../../common/icons/Icon'; import PeerPicker from '../../common/pickers/PeerPicker'; -import Button from '../../ui/Button'; -import Modal from '../../ui/Modal'; +import PickerModal from '../../common/pickers/PickerModal'; import styles from './StarsGiftingPickerModal.module.scss'; @@ -42,8 +38,6 @@ const StarsGiftingPickerModal: FC = ({ archivedListIds, userIds, }) => { - // eslint-disable-next-line no-null/no-null - const dialogRef = useRef(null); const { closeStarsGiftingModal, openStarsGiftModal } = getActions(); const oldLang = useOldLang(); @@ -79,48 +73,30 @@ const StarsGiftingPickerModal: FC = ({ } }); - function renderHeaderText() { - return ( -
- -

{oldLang('GiftStarsTitle')} -

-
- ); - } - return ( - -
- {renderHeaderText()} - -
-
+ + ); }; diff --git a/src/components/middle/ActionMessage.tsx b/src/components/middle/ActionMessage.tsx index 8fcce92f4..17d3fa9e9 100644 --- a/src/components/middle/ActionMessage.tsx +++ b/src/components/middle/ActionMessage.tsx @@ -21,12 +21,14 @@ import { selectChat, selectChatMessage, selectGiftStickerForDuration, + selectGiftStickerForStars, selectIsMessageFocused, selectTabState, selectTopicFromMessage, selectUser, } from '../../global/selectors'; import buildClassName from '../../util/buildClassName'; +import { formatInteger } from '../../util/textFormat'; import { renderActionMessageText } from '../common/helpers/renderActionMessageText'; import renderText from '../common/helpers/renderText'; import { preventMessageInputBlur } from './helpers/preventMessageInputBlur'; @@ -72,6 +74,7 @@ type StateProps = { focusDirection?: FocusDirection; noFocusHighlight?: boolean; premiumGiftSticker?: ApiSticker; + starGiftSticker?: ApiSticker; canPlayAnimatedEmojis?: boolean; }; @@ -93,6 +96,7 @@ const ActionMessage: FC = ({ focusDirection, noFocusHighlight, premiumGiftSticker, + starGiftSticker, isInsideTopic, topic, memoFirstUnreadIdRef, @@ -315,15 +319,16 @@ const ActionMessage: FC = ({ > - - {oldLang('Stars', message.content.action!.stars)} - - +
+ {formatInteger(message.content.action!.stars!)} + {oldLang('Stars')} +
+ {renderText( oldLang(!message.isOutgoing ? 'ActionGiftStarsSubtitleYou' : 'ActionGiftStarsSubtitle', getChatTitle(oldLang, targetChat!)), @@ -406,6 +411,10 @@ export default memo(withGlobal( const giftDuration = content.action?.months; const premiumGiftSticker = selectGiftStickerForDuration(global, giftDuration); + + const starCount = content.action?.stars; + const starGiftSticker = selectGiftStickerForStars(global, starCount); + const topic = selectTopicFromMessage(global, message); return { @@ -417,6 +426,7 @@ export default memo(withGlobal( targetMessage, isFocused, premiumGiftSticker, + starGiftSticker, topic, canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global), ...(isFocused && { diff --git a/src/components/middle/MessageList.scss b/src/components/middle/MessageList.scss index effcdfcd1..33b254dd1 100644 --- a/src/components/middle/MessageList.scss +++ b/src/components/middle/MessageList.scss @@ -273,7 +273,7 @@ } .action-message-gift-code { - width: 20rem; + width: 12rem; margin-inline: auto; } @@ -282,12 +282,25 @@ margin-inline: auto; } + .action-message-stars-balance { + margin-top: 0.5rem; + display: flex; + gap: 0.25rem; + line-height: 1.5; + font-weight: 500; + } + .action-message-subtitle { margin-top: 1rem; font-weight: normal; text-wrap: balance; } + .action-message-stars-subtitle { + font-weight: normal; + text-wrap: balance; + } + .action-message-suggested-avatar { max-width: 16rem; display: flex !important; diff --git a/src/components/modals/common/TableInfoModal.tsx b/src/components/modals/common/TableInfoModal.tsx index 201c7dfc9..429b5d1a4 100644 --- a/src/components/modals/common/TableInfoModal.tsx +++ b/src/components/modals/common/TableInfoModal.tsx @@ -30,6 +30,7 @@ type OwnProps = { headerAvatarPeer?: ApiPeer | CustomPeer; headerAvatarWebPhoto?: ApiWebDocument; noHeaderImage?: boolean; + isGift?: boolean; header?: TeactNode; footer?: TeactNode; buttonText?: string; @@ -47,6 +48,7 @@ const TableInfoModal = ({ headerAvatarPeer, headerAvatarWebPhoto, noHeaderImage, + isGift, header, footer, buttonText, @@ -73,7 +75,7 @@ const TableInfoModal = ({ contentClassName={styles.content} onClose={onClose} > - {!noHeaderImage && ( + {!isGift && !noHeaderImage && ( withAvatar ? ( ) : ( diff --git a/src/components/modals/stars/StarTopupOptionList.module.scss b/src/components/modals/stars/StarTopupOptionList.module.scss index 1f4da3a47..0dcf8090d 100644 --- a/src/components/modals/stars/StarTopupOptionList.module.scss +++ b/src/components/modals/stars/StarTopupOptionList.module.scss @@ -1,5 +1,12 @@ @use '../../../styles/mixins'; +.options { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.5rem; + width: 100%; +} + .option { --_background-color: var(--color-background-secondary); display: flex; @@ -60,3 +67,9 @@ margin-inline-start: 0.25rem; font-size: 1.5rem; } + +@media (max-width: 450px) { + .optionTop { + font-size: 1rem; + } +} diff --git a/src/components/modals/stars/StarTopupOptionList.tsx b/src/components/modals/stars/StarTopupOptionList.tsx index eb70663c6..7dfd5039c 100644 --- a/src/components/modals/stars/StarTopupOptionList.tsx +++ b/src/components/modals/stars/StarTopupOptionList.tsx @@ -73,7 +73,7 @@ const StarTopupOptionList: FC = ({ }, [areOptionsExtended, options, starsNeeded]); return ( - <> +
{renderingOptions?.map(({ option, starsCount, isWide }) => { const length = renderingOptions?.length; const isOdd = length % 2 === 0; @@ -103,7 +103,7 @@ const StarTopupOptionList: FC = ({ )} - +
); }; diff --git a/src/components/modals/stars/StarsBalanceModal.module.scss b/src/components/modals/stars/StarsBalanceModal.module.scss index 0077575c8..309cbaa8b 100644 --- a/src/components/modals/stars/StarsBalanceModal.module.scss +++ b/src/components/modals/stars/StarsBalanceModal.module.scss @@ -136,23 +136,10 @@ z-index: 3; } -.options { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 0.5rem; - width: 100%; -} - -.optionFullWidth { - grid-column: 1 / -1; -} - .starButton { grid-column: 1/-1; - display: flex; - justify-content: center; - align-items: center; gap: 1rem; + margin-top: 0.5rem; } .paymentContent { diff --git a/src/components/modals/stars/StarsBalanceModal.tsx b/src/components/modals/stars/StarsBalanceModal.tsx index 7240228b8..a1a49fd84 100644 --- a/src/components/modals/stars/StarsBalanceModal.tsx +++ b/src/components/modals/stars/StarsBalanceModal.tsx @@ -3,7 +3,7 @@ import React, { } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; -import type { ApiStarTopupOption, ApiUser } from '../../../api/types'; +import type { ApiUser } from '../../../api/types'; import type { GlobalState, TabState } from '../../../global/types'; import { getUserFullName } from '../../../global/helpers'; @@ -24,7 +24,6 @@ import Modal from '../../ui/Modal'; import TabList, { type TabWithProperties } from '../../ui/TabList'; import Transition from '../../ui/Transition'; import BalanceBlock from './BalanceBlock'; -import StarTopupOptionList from './StarTopupOptionList'; import TransactionItem from './transaction/StarsTransactionItem'; import styles from './StarsBalanceModal.module.scss'; @@ -53,10 +52,10 @@ const StarsBalanceModal = ({ modal, starsBalanceState, originPaymentBot, canBuyPremium, }: OwnProps & StateProps) => { const { - closeStarsBalanceModal, loadStarsTransactions, openInvoice, openStarsGiftingModal, + closeStarsBalanceModal, loadStarsTransactions, openStarsGiftingModal, openStarsGiftModal, } = getActions(); - const { balance, history, topupOptions } = starsBalanceState || {}; + const { balance, history } = starsBalanceState || {}; const oldLang = useOldLang(); const lang = useLang(); @@ -96,36 +95,20 @@ const StarsBalanceModal = ({ setHeaderHidden(scrollTop <= 150); } - const handleClick = useLastCallback((option: ApiStarTopupOption) => { - openInvoice({ - type: 'stars', - stars: option.stars, - currency: option.currency, - amount: option.amount, - }); - }); - - function renderStarOptionList() { - return ( - - ); - } - const handleLoadMore = useLastCallback(() => { loadStarsTransactions({ type: TRANSACTION_TYPES[selectedTabIndex], }); }); - const openPremiumGiftingModalHandler = useLastCallback(() => { + const openStarsGiftingModalHandler = useLastCallback(() => { openStarsGiftingModal({}); }); + const openStarsInfoModalHandler = useLastCallback(() => { + openStarsGiftModal({}); + }); + return (
@@ -158,19 +141,24 @@ const StarsBalanceModal = ({ ['simple_markdown', 'emoji'], )}
-
- {renderStarOptionList()} - {canBuyPremium && ( - - )} -
+ {canBuyPremium && ( + + )} + {canBuyPremium && ( + + )}
{tosText} diff --git a/src/components/modals/stars/transaction/StarsTransactionModal.module.scss b/src/components/modals/stars/transaction/StarsTransactionModal.module.scss index deb482d33..c92d4501b 100644 --- a/src/components/modals/stars/transaction/StarsTransactionModal.module.scss +++ b/src/components/modals/stars/transaction/StarsTransactionModal.module.scss @@ -2,6 +2,10 @@ z-index: calc(var(--z-media-viewer) - 1); } +.modal :global(.modal-dialog) { + max-width: 26rem !important; +} + .positive { color: var(--color-success); } @@ -19,10 +23,14 @@ position: relative; } +.starsHeader { + gap: normal; +} + .amount { display: flex; gap: 0.25rem; - font-size: 1.25rem; + font-size: 1rem; font-weight: 500; line-height: 1.325; } @@ -44,6 +52,7 @@ .footer { text-align: center; margin-block: 0.5rem; + color: var(--color-text-secondary); } .starsBackground { @@ -67,3 +76,12 @@ margin-bottom: 2rem; cursor: var(--custom-cursor, pointer); } + +.starTitle { + font-size: 1.5rem; +} + +.subtitle { + text-align: center; + margin-top: 1rem; +} diff --git a/src/components/modals/stars/transaction/StarsTransactionModal.tsx b/src/components/modals/stars/transaction/StarsTransactionModal.tsx index a63c75e26..ff7340e4d 100644 --- a/src/components/modals/stars/transaction/StarsTransactionModal.tsx +++ b/src/components/modals/stars/transaction/StarsTransactionModal.tsx @@ -4,22 +4,29 @@ import { getActions, withGlobal } from '../../../../global'; import type { ApiPeer, - ApiStarsTransactionPeer, + ApiStarsTransactionPeer, ApiSticker, ApiUser, } from '../../../../api/types'; import type { TabState } from '../../../../global/types'; import { MediaViewerOrigin } from '../../../../types'; -import { getMessageLink } from '../../../../global/helpers'; +import { getMessageLink, getUserFullName } from '../../../../global/helpers'; import { buildStarsTransactionCustomPeer, formatStarsTransactionAmount } from '../../../../global/helpers/payments'; -import { selectPeer } from '../../../../global/selectors'; +import { + selectCanPlayAnimatedEmojis, + selectGiftStickerForStars, + selectPeer, selectUser, +} from '../../../../global/selectors'; import buildClassName from '../../../../util/buildClassName'; import { copyTextToClipboard } from '../../../../util/clipboard'; 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 usePrevious from '../../../../hooks/usePrevious'; +import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker'; import Icon from '../../../common/icons/Icon'; import StarIcon from '../../../common/icons/StarIcon'; import SafeLink from '../../../common/SafeLink'; @@ -36,14 +43,19 @@ export type OwnProps = { type StateProps = { peer?: ApiPeer; + user?: ApiUser; + canPlayAnimatedEmojis?: boolean; + starGiftSticker?: ApiSticker; }; const StarsTransactionModal: FC = ({ - modal, peer, + modal, peer, user, canPlayAnimatedEmojis, starGiftSticker, }) => { const { showNotification, openMediaViewer, closeStarsTransactionModal } = getActions(); - const lang = useOldLang(); + const oldLang = useOldLang(); + const lang = useLang(); const { transaction } = modal || {}; + const isGift = transaction?.isGift; const handleOpenMedia = useLastCallback(() => { const media = transaction?.extendedMedia; @@ -55,6 +67,60 @@ const StarsTransactionModal: FC = ({ }); }); + const animatedStickerData = useMemo(() => { + if (!transaction) { + return undefined; + } + + return ( + + ); + }, [canPlayAnimatedEmojis, starGiftSticker, transaction]); + + const giftEntryAboutText = useMemo(() => { + const subtitleText = oldLang('lng_credits_box_history_entry_gift_in_about'); + const subtitleTextParts = subtitleText.split('{link}'); + + return ( + <> + {subtitleTextParts[0]} + + {renderText(oldLang('GiftStarsSubtitleLinkName'), ['simple_markdown'])} + + {subtitleTextParts[1]} + + ); + }, [oldLang]); + + const giftOutAboutText = useMemo(() => { + return lang( + 'CreditsBoxHistoryEntryGiftOutAbout', + { + user: {user ? getUserFullName(user) : ''}, + link: ( + + {renderText(oldLang('GiftStarsSubtitleLinkName'), ['simple_markdown'])} + + ), + }, + { + withNodes: true, + }, + ); + }, [lang, user, oldLang]); + const starModalData = useMemo(() => { if (!transaction) { return undefined; @@ -64,9 +130,9 @@ const StarsTransactionModal: FC = ({ && buildStarsTransactionCustomPeer(transaction.peer)) || undefined; const peerId = transaction.peer?.type === 'peer' ? transaction.peer.id : undefined; - const toName = transaction.peer && lang(getStarsPeerTitleKey(transaction.peer)); + const toName = transaction.peer && oldLang(getStarsPeerTitleKey(transaction.peer)); - const title = transaction.title || (customPeer ? lang(customPeer.titleKey) : undefined); + const title = transaction.title || (customPeer ? oldLang(customPeer.titleKey) : undefined); const messageLink = peer && transaction.messageId ? getMessageLink(peer, undefined, transaction.messageId) : undefined; @@ -77,14 +143,14 @@ const StarsTransactionModal: FC = ({ 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 mediaText = areAllPhotos ? oldLang('Stars.Transfer.Photos', mediaAmount) + : areAllVideos ? oldLang('Stars.Transfer.Videos', mediaAmount) + : oldLang('Media', mediaAmount); const description = transaction.description || (media ? mediaText : undefined); const header = ( -
+
{media && ( = ({ onClick={handleOpenMedia} /> )} - + {isGift ? animatedStickerData : ( + + )} {title &&

{title}

} + {isGift && ( +

+ {transaction?.isMyGift ? oldLang('StarsGiftSent') : oldLang('StarsGiftReceived')} +

+ )}

{description}

{formatStarsTransactionAmount(transaction.stars)} - +

+ {isGift && ( + + {transaction?.isMyGift ? giftOutAboutText : giftEntryAboutText} + + )}
); const tableData: TableData = []; tableData.push([ - lang(transaction.stars < 0 || transaction.isMyGift ? 'Stars.Transaction.To' + oldLang(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'), ]); + tableData.push([oldLang('Stars.Transaction.Media'), ]); } if (transaction.id) { tableData.push([ - lang('Stars.Transaction.Id'), + oldLang('Stars.Transaction.Id'), ( { copyTextToClipboard(transaction.id!); showNotification({ - message: lang('StarsTransactionIDCopied'), + message: oldLang('StarsTransactionIDCopied'), }); }} > @@ -142,17 +220,17 @@ const StarsTransactionModal: FC = ({ } tableData.push([ - lang('Stars.Transaction.Date'), - formatDateTimeToString(transaction.date * 1000, lang.code, true), + oldLang('Stars.Transaction.Date'), + formatDateTimeToString(transaction.date * 1000, oldLang.code, true), ]); - const footerText = lang('lng_credits_box_out_about'); + const footerText = oldLang('lng_credits_box_out_about'); const footerTextParts = footerText.split('{link}'); const footer = ( {footerTextParts[0]} - + {footerTextParts[1]} ); @@ -163,7 +241,7 @@ const StarsTransactionModal: FC = ({ footer, avatarPeer: !transaction.photo ? (peer || customPeer) : undefined, }; - }, [lang, transaction, peer]); + }, [transaction, oldLang, peer, isGift, animatedStickerData, giftOutAboutText, giftEntryAboutText]); const prevModalData = usePrevious(starModalData); const renderingModalData = prevModalData || starModalData; @@ -173,12 +251,13 @@ const StarsTransactionModal: FC = ({ isOpen={Boolean(transaction)} className={styles.modal} header={renderingModalData?.header} + isGift={isGift} tableData={renderingModalData?.tableData} footer={renderingModalData?.footer} noHeaderImage={Boolean(transaction?.extendedMedia)} headerAvatarWebPhoto={transaction?.photo} headerAvatarPeer={renderingModalData?.avatarPeer} - buttonText={lang('OK')} + buttonText={oldLang('OK')} onClose={closeStarsTransactionModal} /> ); @@ -188,9 +267,16 @@ 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; + const user = peerId ? selectUser(global, peerId) : undefined; + + const starCount = modal?.transaction.stars; + const starGiftSticker = selectGiftStickerForStars(global, starCount); return { peer, + user, + canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global), + starGiftSticker, }; }, )(StarsTransactionModal)); diff --git a/src/global/actions/api/payments.ts b/src/global/actions/api/payments.ts index cd926b182..56988d3ca 100644 --- a/src/global/actions/api/payments.ts +++ b/src/global/actions/api/payments.ts @@ -583,7 +583,8 @@ addActionHandler('closePremiumGiftModal', (global, actions, payload): ActionRetu addActionHandler('openStarsGiftModal', async (global, actions, payload): Promise => { const { - forUserId, tabId = getCurrentTabId(), + forUserId, + tabId = getCurrentTabId(), } = payload || {}; const starsGiftOptions = await callApi('getStarsGiftOptions', {}); diff --git a/src/global/actions/apiUpdaters/payments.ts b/src/global/actions/apiUpdaters/payments.ts index 388a565e5..5121ae3b1 100644 --- a/src/global/actions/apiUpdaters/payments.ts +++ b/src/global/actions/apiUpdaters/payments.ts @@ -64,6 +64,23 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { } } + if (inputInvoice?.type === 'stars') { + if (!inputInvoice.stars) { + return; + } + const starsModalState = selectTabState(global, tabId).starsGiftModal; + + if (starsModalState && starsModalState.isOpen) { + global = updateTabState(global, { + starsGiftModal: { + ...starsModalState, + isCompleted: true, + }, + }, tabId); + global = closeInvoice(global, tabId); + } + } + setGlobal(global); }); diff --git a/src/global/selectors/symbols.ts b/src/global/selectors/symbols.ts index a713fb96a..3a37c7091 100644 --- a/src/global/selectors/symbols.ts +++ b/src/global/selectors/symbols.ts @@ -15,6 +15,12 @@ const MONTH_EMOTICON: Record = { 24: `${5}\u{FE0F}\u20E3`, }; +const STAR_EMOTICON: Record = { + 1000: `${2}\u{FE0F}\u20E3`, + 2500: `${3}\u{FE0F}\u20E3`, + 5000: `${4}\u{FE0F}\u20E3`, +}; + export function selectIsStickerFavorite(global: T, sticker: ApiSticker) { const { stickers } = global.stickers.favorite; return stickers && stickers.some(({ id }) => id === sticker.id); @@ -156,3 +162,20 @@ export function selectGiftStickerForDuration(global: T, d const emoji = MONTH_EMOTICON[duration]; return stickers.find((sticker) => sticker.emoji === emoji) || stickers[0]; } + +export function selectGiftStickerForStars(global: T, starCount?: number) { + const stickers = global.premiumGifts?.stickers; + + if (!stickers || !starCount) return undefined; + + let emoji; + if (starCount <= 1000) { + emoji = STAR_EMOTICON[1000]; + } else if (starCount < 2500) { + emoji = STAR_EMOTICON[2500]; + } else { + emoji = STAR_EMOTICON[5000]; + } + + return stickers.find((sticker) => sticker.emoji === emoji) || stickers[0]; +} diff --git a/src/global/types.ts b/src/global/types.ts index 61e3127ef..b3d5880d8 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -63,7 +63,7 @@ import type { ApiSendMessageAction, ApiSession, ApiSessionData, - ApiSponsoredMessage, ApiStarsGiftOption, + ApiSponsoredMessage, ApiStarsTransaction, ApiStarTopupOption, ApiStealthMode, @@ -725,7 +725,7 @@ export type TabState = { isCompleted?: boolean; isOpen?: boolean; forUserId?: string; - starsGiftOptions?: ApiStarsGiftOption[]; + starsGiftOptions?: ApiStarTopupOption[]; }; starsTransactionModal?: { @@ -737,7 +737,7 @@ export type TabState = { isOpen?: boolean; forUserIds?: string[]; gifts?: ApiPremiumGiftCodeOption[]; - starsGiftOptions?: ApiStarsGiftOption[]; + starsGiftOptions?: ApiStarTopupOption[]; }; limitReachedModal?: {