From bcb276d5686ef231e8b1b2acadff9a4b8335ff0f Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Tue, 13 Jan 2026 01:14:20 +0100 Subject: [PATCH] Gift Auction Modal: Add upgrade preview in header (#6573) --- src/api/gramjs/apiBuilders/gifts.ts | 4 +- src/api/types/payments.ts | 5 +- src/api/types/stars.ts | 1 + src/assets/localization/fallback.strings | 12 ++ src/bundles/stars.ts | 1 + src/components/common/helpers/gifts.ts | 18 ++ .../main/premium/PremiumFeatureModal.tsx | 14 +- .../PremiumFeaturePreviewVideo.module.scss | 15 +- .../previews/PremiumFeaturePreviewVideo.tsx | 52 +++--- src/components/modals/ModalContainer.tsx | 3 + .../modals/common/TableAboutModal.tsx | 5 +- .../modals/common/TableInfoModal.tsx | 7 +- .../modals/gift/AboutStarGiftModal.async.tsx | 14 ++ .../gift/AboutStarGiftModal.module.scss | 53 ++++++ .../modals/gift/AboutStarGiftModal.tsx | 93 ++++++++++ .../modals/gift/UniqueGiftHeader.module.scss | 33 +++- .../modals/gift/UniqueGiftHeader.tsx | 6 + .../gift/auction/GiftAuctionModal.module.scss | 6 + .../modals/gift/auction/GiftAuctionModal.tsx | 175 +++++++++++++++++- .../modals/gift/info/GiftInfoModal.tsx | 65 +------ .../modals/gift/upgrade/GiftUpgradeModal.tsx | 23 +-- src/components/ui/Modal.scss | 7 + src/components/ui/Modal.tsx | 58 +++++- src/global/actions/api/stars.ts | 10 +- src/global/actions/ui/chats.ts | 1 + src/global/actions/ui/stars.ts | 26 +++ src/global/types/actions.ts | 2 + src/global/types/tabState.ts | 8 + src/types/language.d.ts | 16 ++ 29 files changed, 611 insertions(+), 122 deletions(-) create mode 100644 src/components/modals/gift/AboutStarGiftModal.async.tsx create mode 100644 src/components/modals/gift/AboutStarGiftModal.module.scss create mode 100644 src/components/modals/gift/AboutStarGiftModal.tsx diff --git a/src/api/gramjs/apiBuilders/gifts.ts b/src/api/gramjs/apiBuilders/gifts.ts index 08601c746..8ae4994de 100644 --- a/src/api/gramjs/apiBuilders/gifts.ts +++ b/src/api/gramjs/apiBuilders/gifts.ts @@ -63,7 +63,8 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift { const { id, limited, stars, availabilityRemains, availabilityTotal, convertStars, firstSaleDate, lastSaleDate, soldOut, birthday, upgradeStars, resellMinStars, title, availabilityResale, releasedBy, - requirePremium, limitedPerUser, perUserTotal, perUserRemains, lockedUntilDate, auction, giftsPerRound, background, + requirePremium, limitedPerUser, perUserTotal, perUserRemains, lockedUntilDate, auction, auctionSlug, giftsPerRound, + background, } = starGift; addDocumentToLocalDb(starGift.sticker); @@ -94,6 +95,7 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift { perUserRemains, lockedUntilDate, isAuction: auction, + auctionSlug, giftsPerRound, background: background ? { centerColor: int2hex(background.centerColor), diff --git a/src/api/types/payments.ts b/src/api/types/payments.ts index 07490398d..3ae98c067 100644 --- a/src/api/types/payments.ts +++ b/src/api/types/payments.ts @@ -119,8 +119,11 @@ export type ApiReceipt = ApiReceiptRegular | ApiReceiptStars; export type ApiPremiumSection = typeof PREMIUM_FEATURE_SECTIONS[number]; +// Video sections can include additional values like 'gifts' that are not premium features +export type ApiPromoVideoSection = ApiPremiumSection | 'gifts'; + export interface ApiPremiumPromo { - videoSections: ApiPremiumSection[]; + videoSections: ApiPromoVideoSection[]; videos: ApiDocument[]; statusText: string; statusEntities: ApiMessageEntity[]; diff --git a/src/api/types/stars.ts b/src/api/types/stars.ts index f89f1e0f5..74ed6df20 100644 --- a/src/api/types/stars.ts +++ b/src/api/types/stars.ts @@ -28,6 +28,7 @@ export interface ApiStarGiftRegular { perUserRemains?: number; lockedUntilDate?: number; isAuction?: true; + auctionSlug?: string; giftsPerRound?: number; background?: ApiStarGiftBackground; } diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index 534ede7d0..d554ae5af 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -2342,6 +2342,8 @@ "ContextMenuHintTouch" = "To edit or reply, close this menu. Then long tap on a message."; "GiftValueForSaleOnFragment" = "for sale on Fragment"; "GiftValueForSaleOnTelegram" = "for sale on Telegram"; +"GiftAuctionForSaleOnFragment" = "{count} for sale on Fragment >"; +"GiftAuctionForSaleOnTelegram" = "{count} for sale on Telegram >"; "EmbeddedMessageNoCaption" = "Caption removed"; "ConfirmBuyGiftForTonDescription" = "The seller only accepts TON as payment."; "TitleGiftLocked" = "Gift Locked"; @@ -2478,6 +2480,16 @@ "GiftAuctionAveragePrice" = "Average Price"; "GiftAuctionTapToBidMore" = "click to bid more"; "GiftAuctionWonNotification" = "You won {gift} at the auction!"; +"GiftAuctionLearnMoreAboutGifts" = "Learn more about Telegram Gifts >"; +"GiftAuctionLearnMoreMenuItem" = "Learn More"; +"StarGiftInfoTitle" = "Telegram Gifts"; +"StarGiftInfoSubtitle" = "Gifts are collectible items you can trade or showcase on your profile."; +"StarGiftInfoUniqueTitle" = "Unique"; +"StarGiftInfoUniqueSubtitle" = "Upgrade your gifts to get a unique number, model, backdrop and symbol."; +"StarGiftInfoTradableTitle" = "Tradable"; +"StarGiftInfoTradableSubtitle" = "Sell your gift on Telegram or on third-party NFT marketplaces."; +"StarGiftInfoWearableTitle" = "Wearable"; +"StarGiftInfoWearableSubtitle" = "Display gifts on your profile and set them as your cover or status."; "StarGift" = "Star Gift"; "SettingsItemPrivacyPasskeys" = "Passkeys"; "SettingsItemPrivacyOn" = "Enabled"; diff --git a/src/bundles/stars.ts b/src/bundles/stars.ts index 5a29a7098..61da1efb3 100644 --- a/src/bundles/stars.ts +++ b/src/bundles/stars.ts @@ -20,6 +20,7 @@ export { default as GiftAuctionInfoModal } from '../components/modals/gift/aucti export { default as GiftAuctionAcquiredModal } from '../components/modals/gift/auction/GiftAuctionAcquiredModal'; export { default as GiftAuctionChangeRecipientModal } from '../components/modals/gift/auction/GiftAuctionChangeRecipientModal'; export { default as StarGiftPriceDecreaseInfoModal } from '../components/modals/gift/StarGiftPriceDecreaseInfoModal'; +export { default as AboutStarGiftModal } from '../components/modals/gift/AboutStarGiftModal'; export { default as GiftStatusInfoModal } from '../components/modals/gift/status/GiftStatusInfoModal'; export { default as GiftWithdrawModal } from '../components/modals/gift/withdraw/GiftWithdrawModal'; export { default as GiftTransferModal } from '../components/modals/gift/transfer/GiftTransferModal'; diff --git a/src/components/common/helpers/gifts.ts b/src/components/common/helpers/gifts.ts index e360ae265..e338e6346 100644 --- a/src/components/common/helpers/gifts.ts +++ b/src/components/common/helpers/gifts.ts @@ -7,6 +7,10 @@ import type { ApiStarGiftAttributePattern, ApiSticker, } from '../../../api/types'; +import { ApiMediaFormat } from '../../../api/types'; + +import { getStickerMediaHash } from '../../../global/helpers'; +import { fetch } from '../../../util/mediaLoader'; export type GiftAttributes = { model?: ApiStarGiftAttributeModel; @@ -101,3 +105,17 @@ export function getRandomGiftPreviewAttributes( backdrop: randomBackdrop, }; } + +export function preloadGiftAttributeStickers(attributes: ApiStarGiftAttribute[]) { + const patternStickers = attributes + .filter((attr): attr is ApiStarGiftAttributePattern => attr.type === 'pattern') + .map((attr) => attr.sticker); + const modelStickers = attributes + .filter((attr): attr is ApiStarGiftAttributeModel => attr.type === 'model') + .map((attr) => attr.sticker); + + const mediaHashes = [...patternStickers, ...modelStickers].map((sticker) => getStickerMediaHash(sticker, 'full')); + mediaHashes.forEach((hash) => { + fetch(hash, ApiMediaFormat.BlobUrl); + }); +} diff --git a/src/components/main/premium/PremiumFeatureModal.tsx b/src/components/main/premium/PremiumFeatureModal.tsx index 4779b8758..72b9cd50b 100644 --- a/src/components/main/premium/PremiumFeatureModal.tsx +++ b/src/components/main/premium/PremiumFeatureModal.tsx @@ -291,14 +291,14 @@ const PremiumFeatureModal: FC = ({ const i = promo.videoSections.indexOf(section); if (i === -1) return undefined; - const shouldUseNewLang = promo.videoSections[i] === 'todo'; + const shouldUseNewLang = section === 'todo'; return (
= ({

{shouldUseNewLang ? lang( - PREMIUM_FEATURE_TITLES[promo.videoSections[i]] as keyof LangPair, + PREMIUM_FEATURE_TITLES['todo'] as keyof LangPair, undefined, { withNodes: true, renderTextFilters: ['br'] }, ) - : oldLang(PREMIUM_FEATURE_TITLES[promo.videoSections[i]])} + : oldLang(PREMIUM_FEATURE_TITLES[section])}

{renderText(shouldUseNewLang ? lang( - PREMIUM_FEATURE_DESCRIPTIONS[promo.videoSections[i]] as keyof LangPair, + PREMIUM_FEATURE_DESCRIPTIONS['todo'] as keyof LangPair, undefined, { withNodes: true, renderTextFilters: ['br'] }, ) - : oldLang(PREMIUM_FEATURE_DESCRIPTIONS[promo.videoSections[i]]), ['br'], + : oldLang(PREMIUM_FEATURE_DESCRIPTIONS[section]), ['br'], )}
diff --git a/src/components/main/premium/previews/PremiumFeaturePreviewVideo.module.scss b/src/components/main/premium/previews/PremiumFeaturePreviewVideo.module.scss index 4e3b5d667..2683948f7 100644 --- a/src/components/main/premium/previews/PremiumFeaturePreviewVideo.module.scss +++ b/src/components/main/premium/previews/PremiumFeaturePreviewVideo.module.scss @@ -35,7 +35,7 @@ } .down { - --y-static: 3%; + --y-static: 2%; --y-dynamic: 10%; } @@ -67,3 +67,16 @@ bottom: initial; border-radius: 10% 10% 0 0; } + +.placeholder { + --placeholder-background: #555555; + + position: absolute; + z-index: 0; + + width: 97%; + height: 97%; + border-radius: 10%; + + background-color: var(--placeholder-background); +} diff --git a/src/components/main/premium/previews/PremiumFeaturePreviewVideo.tsx b/src/components/main/premium/previews/PremiumFeaturePreviewVideo.tsx index dd79d338d..52d8dec8b 100644 --- a/src/components/main/premium/previews/PremiumFeaturePreviewVideo.tsx +++ b/src/components/main/premium/previews/PremiumFeaturePreviewVideo.tsx @@ -16,47 +16,55 @@ import styles from './PremiumFeaturePreviewVideo.module.scss'; import DeviceFrame from '../../../../assets/premium/DeviceFrame.svg'; type OwnProps = { - videoId: string; - isReverseAnimation: boolean; - isDown: boolean; - videoThumbnail: ApiThumbnail; - index: number; - isActive: boolean; + videoId?: string; + videoThumbnail?: ApiThumbnail; + isActive?: boolean; + isReverseAnimation?: boolean; + isDown?: boolean; + index?: number; + className?: string; + wrapperClassName?: string; }; const PremiumFeaturePreviewVideo: FC = ({ videoId, + videoThumbnail, + isActive, isReverseAnimation, isDown, - videoThumbnail, index, - isActive, + className, + wrapperClassName, }) => { - const mediaData = useMedia(`document${videoId}`); - const thumbnailRef = useCanvasBlur(videoThumbnail.dataUri); + const mediaData = useMedia(videoId ? `document${videoId}` : undefined); + const thumbnailRef = useCanvasBlur(videoThumbnail?.dataUri); const transitionClassNames = useMediaTransitionDeprecated(mediaData); return ( -
+
- - + {!videoId &&
} + {videoThumbnail && } + {videoId && ( + + )}
); diff --git a/src/components/modals/ModalContainer.tsx b/src/components/modals/ModalContainer.tsx index 87a079346..4f49eddfd 100644 --- a/src/components/modals/ModalContainer.tsx +++ b/src/components/modals/ModalContainer.tsx @@ -19,6 +19,7 @@ import CollectibleInfoModal from './collectible/CollectibleInfoModal.async'; import DeleteAccountModal from './deleteAccount/DeleteAccountModal.async'; import EmojiStatusAccessModal from './emojiStatusAccess/EmojiStatusAccessModal.async'; import FrozenAccountModal from './frozenAccount/FrozenAccountModal.async'; +import AboutStarGiftModal from './gift/AboutStarGiftModal.async'; import GiftAuctionAcquiredModal from './gift/auction/GiftAuctionAcquiredModal.async'; import GiftAuctionBidModal from './gift/auction/GiftAuctionBidModal.async'; import GiftAuctionChangeRecipientModal from './gift/auction/GiftAuctionChangeRecipientModal.async'; @@ -105,6 +106,7 @@ type ModalKey = keyof Pick {headerIconName && ( diff --git a/src/components/modals/common/TableInfoModal.tsx b/src/components/modals/common/TableInfoModal.tsx index d5cd41f2f..0dce2ee5a 100644 --- a/src/components/modals/common/TableInfoModal.tsx +++ b/src/components/modals/common/TableInfoModal.tsx @@ -30,6 +30,8 @@ type OwnProps = { contentClassName?: string; tableClassName?: string; hasBackdrop?: boolean; + closeButtonColor?: 'translucent' | 'translucent-white'; + moreMenuItems?: TeactNode; onClose: NoneToVoidFunction; onButtonClick?: NoneToVoidFunction; withBalanceBar?: boolean; @@ -50,6 +52,8 @@ const TableInfoModal = ({ contentClassName, tableClassName, hasBackdrop, + closeButtonColor, + moreMenuItems, onClose, onButtonClick, withBalanceBar, @@ -68,12 +72,13 @@ const TableInfoModal = ({ isOpen={isOpen} hasCloseButton={Boolean(title)} hasAbsoluteCloseButton={!title} - absoluteCloseButtonColor={hasBackdrop ? 'translucent-white' : undefined} + absoluteCloseButtonColor={closeButtonColor || (hasBackdrop ? 'translucent-white' : undefined)} isSlim header={modalHeader} title={title} className={className} contentClassName={buildClassName(styles.content, contentClassName)} + moreMenuItems={moreMenuItems} onClose={onClose} withBalanceBar={withBalanceBar} currencyInBalanceBar={currencyInBalanceBar} diff --git a/src/components/modals/gift/AboutStarGiftModal.async.tsx b/src/components/modals/gift/AboutStarGiftModal.async.tsx new file mode 100644 index 000000000..24242f30c --- /dev/null +++ b/src/components/modals/gift/AboutStarGiftModal.async.tsx @@ -0,0 +1,14 @@ +import type { OwnProps } from './AboutStarGiftModal'; + +import { Bundles } from '../../../util/moduleLoader'; + +import useModuleLoader from '../../../hooks/useModuleLoader'; + +const AboutStarGiftModalAsync = (props: OwnProps) => { + const { modal } = props; + const AboutStarGiftModal = useModuleLoader(Bundles.Stars, 'AboutStarGiftModal', !modal); + + return AboutStarGiftModal ? : undefined; +}; + +export default AboutStarGiftModalAsync; diff --git a/src/components/modals/gift/AboutStarGiftModal.module.scss b/src/components/modals/gift/AboutStarGiftModal.module.scss new file mode 100644 index 000000000..80c289b27 --- /dev/null +++ b/src/components/modals/gift/AboutStarGiftModal.module.scss @@ -0,0 +1,53 @@ +.header { + display: flex; + flex-direction: column; + align-items: center; + + width: calc(100% + 2rem); + margin: -1rem -1rem 0.25rem; +} + +.content { + gap: 0.125rem; +} + +.videoPreviewWrapper { + position: relative; + + overflow: hidden; + display: flex; + justify-content: center; + + aspect-ratio: 10 / 8; + width: 100%; + + background: var(--premium-gradient); +} + +.sparkles { + color: white; +} + +.title { + padding-top: 0.5rem; + font-size: 1.5rem; + font-weight: var(--font-weight-medium); + text-align: center; +} + +.subtitle { + padding-top: 0.25rem; + text-align: center; + text-wrap: balance; +} + +.footer { + display: flex; + flex-direction: column; + align-self: stretch; + margin-top: 0.5rem; +} + +.understoodIcon { + font-size: 1.1875rem; +} diff --git a/src/components/modals/gift/AboutStarGiftModal.tsx b/src/components/modals/gift/AboutStarGiftModal.tsx new file mode 100644 index 000000000..d193741a3 --- /dev/null +++ b/src/components/modals/gift/AboutStarGiftModal.tsx @@ -0,0 +1,93 @@ +import { memo, useMemo } from '../../../lib/teact/teact'; +import { getActions } from '../../../global'; + +import type { TabState } from '../../../global/types'; + +import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; + +import Sparkles from '../../common/Sparkles'; +import PremiumFeaturePreviewVideo from '../../main/premium/previews/PremiumFeaturePreviewVideo'; +import Button from '../../ui/Button'; +import TableAboutModal, { type TableAboutData } from '../common/TableAboutModal'; + +import styles from './AboutStarGiftModal.module.scss'; + +export type OwnProps = { + modal: TabState['aboutStarGiftModal']; +}; + +const AboutStarGiftModal = ({ + modal, +}: OwnProps) => { + const { closeAboutStarGiftModal } = getActions(); + const lang = useLang(); + + const isOpen = Boolean(modal?.isOpen); + const renderingModal = useCurrentOrPrev(modal); + + const handleClose = useLastCallback(() => { + closeAboutStarGiftModal(); + }); + + const header = useMemo(() => { + return ( +
+
+ + +
+
+ {lang('StarGiftInfoTitle')} +
+
+ {lang('StarGiftInfoSubtitle')} +
+
+ ); + }, [lang, renderingModal, isOpen]); + + const footer = useMemo(() => { + if (!isOpen) return undefined; + return ( +
+ +
+ ); + }, [lang, isOpen, handleClose]); + + const listItemData = useMemo(() => { + return [ + ['diamond', lang('StarGiftInfoUniqueTitle'), lang('StarGiftInfoUniqueSubtitle')], + ['auction', lang('StarGiftInfoTradableTitle'), lang('StarGiftInfoTradableSubtitle')], + ['crown-wear-outline', lang('StarGiftInfoWearableTitle'), lang('StarGiftInfoWearableSubtitle')], + ] satisfies TableAboutData; + }, [lang]); + + return ( + + ); +}; + +export default memo(AboutStarGiftModal); diff --git a/src/components/modals/gift/UniqueGiftHeader.module.scss b/src/components/modals/gift/UniqueGiftHeader.module.scss index 5e93e11f4..6c6c51dde 100644 --- a/src/components/modals/gift/UniqueGiftHeader.module.scss +++ b/src/components/modals/gift/UniqueGiftHeader.module.scss @@ -25,6 +25,10 @@ } } + &.withBadge { + --_height: 17rem; + } + :global { canvas { transform-origin: center 45%; @@ -33,17 +37,23 @@ } } +.subtitleBadge, .badge { + padding: 0.25rem 0.5rem; + border-radius: 1rem; + + line-height: 0.875rem; + + background-color: rgba(0, 0, 0, 0.2); + backdrop-filter: blur(50px); +} + .subtitleBadge { margin-top: 0.25rem; padding: 0.75rem; padding-block: 0.25rem; - border-radius: 1rem; line-height: 1rem; - background-color: rgba(0, 0, 0, 0.2); - backdrop-filter: blur(50px); - transition: color 150ms ease-in, background-color 0.15s !important; &:hover { @@ -97,6 +107,10 @@ color: white; } +.badge + .title { + margin-top: 0; +} + .subtitle { font-size: 1rem; line-height: 1.375rem; @@ -106,7 +120,16 @@ transition: color 150ms ease-in; } -.title, .subtitle { +.title, .subtitle, .badge { z-index: 1; margin-bottom: 0; } + +.badge { + margin-top: auto; + padding: 0.25rem 0.75rem; + + font-size: 0.8125rem; + font-weight: var(--font-weight-medium); + color: white; +} diff --git a/src/components/modals/gift/UniqueGiftHeader.tsx b/src/components/modals/gift/UniqueGiftHeader.tsx index 298aafd65..924fb49c4 100644 --- a/src/components/modals/gift/UniqueGiftHeader.tsx +++ b/src/components/modals/gift/UniqueGiftHeader.tsx @@ -35,6 +35,7 @@ type OwnProps = { backdropAttribute: ApiStarGiftAttributeBackdrop; patternAttribute: ApiStarGiftAttributePattern; title?: string; + badge?: TeactNode; subtitle?: TeactNode; subtitlePeer?: ApiPeer; className?: string; @@ -50,6 +51,7 @@ const UniqueGiftHeader = ({ backdropAttribute, patternAttribute, title, + badge, subtitle, subtitlePeer, className, @@ -89,6 +91,7 @@ const UniqueGiftHeader = ({
+ {Boolean(badge) && ( +
{badge}
+ )} {title &&

{title}

} {Boolean(subtitle) && (
{ setGiftModalSelectedGift, openGiftAuctionInfoModal, openGiftAuctionAcquiredModal, + openAboutStarGiftModal, + showNotification, + openChatWithDraft, + openUrl, + openGiftInMarket, } = getActions(); const isOpen = Boolean(modal?.isOpen); + const renderingModal = useCurrentOrPrev(modal); const renderingAuctionState = useCurrentOrPrev(auctionState); const gift = renderingAuctionState?.gift; @@ -50,14 +66,43 @@ const GiftAuctionModal = ({ modal, auctionState }: OwnProps & StateProps) => { const userState = renderingAuctionState?.userState; const isFinished = state?.type === 'finished'; + const [previewAttributes, setPreviewAttributes] = useState(); + const shouldUseUniqueHeader = Boolean(gift && state && previewAttributes); + + const uniqueHeaderRef = useRef(); const lang = useLang(); + const updatePreviewAttributes = useLastCallback(() => { + if (!renderingModal?.sampleAttributes) return; + setPreviewAttributes(getRandomGiftPreviewAttributes(renderingModal.sampleAttributes, previewAttributes)); + }); + + useInterval(updatePreviewAttributes, isOpen ? PREVIEW_UPDATE_INTERVAL : undefined, true); + + useEffect(() => { + if (isOpen && renderingModal?.sampleAttributes) { + updatePreviewAttributes(); + } else { + setPreviewAttributes(undefined); + } + }, [isOpen, renderingModal?.sampleAttributes]); + + useEffect(() => { + const attributes = renderingModal?.sampleAttributes; + if (!attributes) return; + preloadGiftAttributeStickers(attributes); + }, [renderingModal?.sampleAttributes]); + const handleClose = useLastCallback(() => closeGiftAuctionModal()); const handleLearnMoreClick = useLastCallback(() => { openGiftAuctionInfoModal({}); }); + const handleLearnMoreAboutGiftsClick = useLastCallback(() => { + openAboutStarGiftModal({}); + }); + const handleItemsBoughtClick = useLastCallback(() => { if (!gift) return; const giftSticker = getStickerFromGift(gift); @@ -70,8 +115,65 @@ const GiftAuctionModal = ({ modal, auctionState }: OwnProps & StateProps) => { setGiftModalSelectedGift({ gift }); }); - const header = useMemo(() => { - if (!gift || !state) { + const auctionLink = useMemo(() => { + if (!gift?.auctionSlug) return undefined; + return `${TME_LINK_PREFIX}auction/${gift.auctionSlug}`; + }, [gift]); + + const handleCopyLink = useLastCallback(() => { + if (!auctionLink) return; + copyTextToClipboard(auctionLink); + showNotification({ + message: lang('LinkCopied'), + }); + }); + + const handleShareLink = useLastCallback(() => { + if (!auctionLink) return; + openChatWithDraft({ text: { text: auctionLink } }); + }); + + const handleOpenFragment = useLastCallback(() => { + if (state?.type === 'finished' && state.fragmentListedUrl) { + openUrl({ url: state.fragmentListedUrl }); + } + }); + + const handleOpenTelegramMarket = useLastCallback(() => { + if (!gift) return; + handleClose(); + openGiftInMarket({ gift }); + }); + + const uniqueHeader = useMemo(() => { + if (!shouldUseUniqueHeader) { + return undefined; + } + + const giftTitle = gift!.title || lang('StarGift'); + const badge = isFinished ? lang('GiftAuctionEnded') : lang('GiftAuctionInfoTitle'); + const subtitle = ( + + {lang('GiftAuctionLearnMoreAboutGifts')} + + ); + + return ( +
+ +
+ ); + }, [shouldUseUniqueHeader, gift, isFinished, lang, previewAttributes, handleLearnMoreAboutGiftsClick]); + + const regularHeader = useMemo(() => { + if (!gift || !state || shouldUseUniqueHeader) { return undefined; } @@ -97,7 +199,9 @@ const GiftAuctionModal = ({ modal, auctionState }: OwnProps & StateProps) => { )}
); - }, [gift, state, isFinished, lang, handleLearnMoreClick]); + }, [gift, state, shouldUseUniqueHeader, isFinished, lang, handleLearnMoreClick]); + + const header = uniqueHeader || regularHeader; const modalData = useMemo(() => { if (!gift || !state || !userState) { @@ -148,6 +252,10 @@ const GiftAuctionModal = ({ modal, auctionState }: OwnProps & StateProps) => { const auctionTimeLeft = state.endDate - getServerTime(); const shouldUseTextTimer = auctionTimeLeft > 0 && auctionTimeLeft < TEXT_TIMER_THRESHOLD; + const canBuyOnFragment = state.type === 'finished' + && Boolean(state.fragmentListedUrl && state.fragmentListedCount); + const canBuyOnTelegram = state.type === 'finished' && Boolean(state.listedCount); + const footer = (
{acquiredCount > 0 && ( @@ -165,6 +273,40 @@ const GiftAuctionModal = ({ modal, auctionState }: OwnProps & StateProps) => { }, { pluralValue: acquiredCount, withNodes: true })} )} + {canBuyOnFragment && ( + + {lang('GiftAuctionForSaleOnFragment', { + count: giftSticker ? ( + <> + {lang.number(state.fragmentListedCount!)} + + + ) : lang.number(state.fragmentListedCount!), + }, { withNodes: true })} + + )} + {canBuyOnTelegram && ( + + {lang('GiftAuctionForSaleOnTelegram', { + count: giftSticker ? ( + <> + {lang.number(state.listedCount!)} + + + ) : lang.number(state.listedCount!), + }, { withNodes: true })} + + )}