Gift Auction Modal: Add upgrade preview in header (#6573)
This commit is contained in:
parent
9308631302
commit
bcb276d568
@ -63,7 +63,8 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
|||||||
const {
|
const {
|
||||||
id, limited, stars, availabilityRemains, availabilityTotal, convertStars, firstSaleDate, lastSaleDate, soldOut,
|
id, limited, stars, availabilityRemains, availabilityTotal, convertStars, firstSaleDate, lastSaleDate, soldOut,
|
||||||
birthday, upgradeStars, resellMinStars, title, availabilityResale, releasedBy,
|
birthday, upgradeStars, resellMinStars, title, availabilityResale, releasedBy,
|
||||||
requirePremium, limitedPerUser, perUserTotal, perUserRemains, lockedUntilDate, auction, giftsPerRound, background,
|
requirePremium, limitedPerUser, perUserTotal, perUserRemains, lockedUntilDate, auction, auctionSlug, giftsPerRound,
|
||||||
|
background,
|
||||||
} = starGift;
|
} = starGift;
|
||||||
|
|
||||||
addDocumentToLocalDb(starGift.sticker);
|
addDocumentToLocalDb(starGift.sticker);
|
||||||
@ -94,6 +95,7 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
|||||||
perUserRemains,
|
perUserRemains,
|
||||||
lockedUntilDate,
|
lockedUntilDate,
|
||||||
isAuction: auction,
|
isAuction: auction,
|
||||||
|
auctionSlug,
|
||||||
giftsPerRound,
|
giftsPerRound,
|
||||||
background: background ? {
|
background: background ? {
|
||||||
centerColor: int2hex(background.centerColor),
|
centerColor: int2hex(background.centerColor),
|
||||||
|
|||||||
@ -119,8 +119,11 @@ export type ApiReceipt = ApiReceiptRegular | ApiReceiptStars;
|
|||||||
|
|
||||||
export type ApiPremiumSection = typeof PREMIUM_FEATURE_SECTIONS[number];
|
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 {
|
export interface ApiPremiumPromo {
|
||||||
videoSections: ApiPremiumSection[];
|
videoSections: ApiPromoVideoSection[];
|
||||||
videos: ApiDocument[];
|
videos: ApiDocument[];
|
||||||
statusText: string;
|
statusText: string;
|
||||||
statusEntities: ApiMessageEntity[];
|
statusEntities: ApiMessageEntity[];
|
||||||
|
|||||||
@ -28,6 +28,7 @@ export interface ApiStarGiftRegular {
|
|||||||
perUserRemains?: number;
|
perUserRemains?: number;
|
||||||
lockedUntilDate?: number;
|
lockedUntilDate?: number;
|
||||||
isAuction?: true;
|
isAuction?: true;
|
||||||
|
auctionSlug?: string;
|
||||||
giftsPerRound?: number;
|
giftsPerRound?: number;
|
||||||
background?: ApiStarGiftBackground;
|
background?: ApiStarGiftBackground;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2342,6 +2342,8 @@
|
|||||||
"ContextMenuHintTouch" = "To edit or reply, close this menu. Then long tap on a message.";
|
"ContextMenuHintTouch" = "To edit or reply, close this menu. Then long tap on a message.";
|
||||||
"GiftValueForSaleOnFragment" = "for sale on Fragment";
|
"GiftValueForSaleOnFragment" = "for sale on Fragment";
|
||||||
"GiftValueForSaleOnTelegram" = "for sale on Telegram";
|
"GiftValueForSaleOnTelegram" = "for sale on Telegram";
|
||||||
|
"GiftAuctionForSaleOnFragment" = "{count} for sale on Fragment >";
|
||||||
|
"GiftAuctionForSaleOnTelegram" = "{count} for sale on Telegram >";
|
||||||
"EmbeddedMessageNoCaption" = "Caption removed";
|
"EmbeddedMessageNoCaption" = "Caption removed";
|
||||||
"ConfirmBuyGiftForTonDescription" = "The seller only accepts TON as payment.";
|
"ConfirmBuyGiftForTonDescription" = "The seller only accepts TON as payment.";
|
||||||
"TitleGiftLocked" = "Gift Locked";
|
"TitleGiftLocked" = "Gift Locked";
|
||||||
@ -2478,6 +2480,16 @@
|
|||||||
"GiftAuctionAveragePrice" = "Average Price";
|
"GiftAuctionAveragePrice" = "Average Price";
|
||||||
"GiftAuctionTapToBidMore" = "click to bid more";
|
"GiftAuctionTapToBidMore" = "click to bid more";
|
||||||
"GiftAuctionWonNotification" = "You won {gift} at the auction!";
|
"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";
|
"StarGift" = "Star Gift";
|
||||||
"SettingsItemPrivacyPasskeys" = "Passkeys";
|
"SettingsItemPrivacyPasskeys" = "Passkeys";
|
||||||
"SettingsItemPrivacyOn" = "Enabled";
|
"SettingsItemPrivacyOn" = "Enabled";
|
||||||
|
|||||||
@ -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 GiftAuctionAcquiredModal } from '../components/modals/gift/auction/GiftAuctionAcquiredModal';
|
||||||
export { default as GiftAuctionChangeRecipientModal } from '../components/modals/gift/auction/GiftAuctionChangeRecipientModal';
|
export { default as GiftAuctionChangeRecipientModal } from '../components/modals/gift/auction/GiftAuctionChangeRecipientModal';
|
||||||
export { default as StarGiftPriceDecreaseInfoModal } from '../components/modals/gift/StarGiftPriceDecreaseInfoModal';
|
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 GiftStatusInfoModal } from '../components/modals/gift/status/GiftStatusInfoModal';
|
||||||
export { default as GiftWithdrawModal } from '../components/modals/gift/withdraw/GiftWithdrawModal';
|
export { default as GiftWithdrawModal } from '../components/modals/gift/withdraw/GiftWithdrawModal';
|
||||||
export { default as GiftTransferModal } from '../components/modals/gift/transfer/GiftTransferModal';
|
export { default as GiftTransferModal } from '../components/modals/gift/transfer/GiftTransferModal';
|
||||||
|
|||||||
@ -7,6 +7,10 @@ import type {
|
|||||||
ApiStarGiftAttributePattern,
|
ApiStarGiftAttributePattern,
|
||||||
ApiSticker,
|
ApiSticker,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
import { ApiMediaFormat } from '../../../api/types';
|
||||||
|
|
||||||
|
import { getStickerMediaHash } from '../../../global/helpers';
|
||||||
|
import { fetch } from '../../../util/mediaLoader';
|
||||||
|
|
||||||
export type GiftAttributes = {
|
export type GiftAttributes = {
|
||||||
model?: ApiStarGiftAttributeModel;
|
model?: ApiStarGiftAttributeModel;
|
||||||
@ -101,3 +105,17 @@ export function getRandomGiftPreviewAttributes(
|
|||||||
backdrop: randomBackdrop,
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -291,14 +291,14 @@ const PremiumFeatureModal: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const i = promo.videoSections.indexOf(section);
|
const i = promo.videoSections.indexOf(section);
|
||||||
if (i === -1) return undefined;
|
if (i === -1) return undefined;
|
||||||
const shouldUseNewLang = promo.videoSections[i] === 'todo';
|
const shouldUseNewLang = section === 'todo';
|
||||||
return (
|
return (
|
||||||
<div className={styles.slide}>
|
<div className={styles.slide}>
|
||||||
<div className={styles.frame}>
|
<div className={styles.frame}>
|
||||||
<PremiumFeaturePreviewVideo
|
<PremiumFeaturePreviewVideo
|
||||||
isActive={currentSlideIndex === index}
|
isActive={currentSlideIndex === index}
|
||||||
videoId={promo.videos[i].id!}
|
videoId={promo.videos[i].id}
|
||||||
videoThumbnail={promo.videos[i].thumbnail!}
|
videoThumbnail={promo.videos[i].thumbnail}
|
||||||
isDown={PREMIUM_BOTTOM_VIDEOS.includes(section)}
|
isDown={PREMIUM_BOTTOM_VIDEOS.includes(section)}
|
||||||
index={index}
|
index={index}
|
||||||
isReverseAnimation={index === reverseAnimationSlideIndex}
|
isReverseAnimation={index === reverseAnimationSlideIndex}
|
||||||
@ -307,20 +307,20 @@ const PremiumFeatureModal: FC<OwnProps> = ({
|
|||||||
<h1 className={styles.title}>
|
<h1 className={styles.title}>
|
||||||
{shouldUseNewLang
|
{shouldUseNewLang
|
||||||
? lang(
|
? lang(
|
||||||
PREMIUM_FEATURE_TITLES[promo.videoSections[i]] as keyof LangPair,
|
PREMIUM_FEATURE_TITLES['todo'] as keyof LangPair,
|
||||||
undefined,
|
undefined,
|
||||||
{ withNodes: true, renderTextFilters: ['br'] },
|
{ withNodes: true, renderTextFilters: ['br'] },
|
||||||
)
|
)
|
||||||
: oldLang(PREMIUM_FEATURE_TITLES[promo.videoSections[i]])}
|
: oldLang(PREMIUM_FEATURE_TITLES[section])}
|
||||||
</h1>
|
</h1>
|
||||||
<div className={styles.description}>
|
<div className={styles.description}>
|
||||||
{renderText(shouldUseNewLang
|
{renderText(shouldUseNewLang
|
||||||
? lang(
|
? lang(
|
||||||
PREMIUM_FEATURE_DESCRIPTIONS[promo.videoSections[i]] as keyof LangPair,
|
PREMIUM_FEATURE_DESCRIPTIONS['todo'] as keyof LangPair,
|
||||||
undefined,
|
undefined,
|
||||||
{ withNodes: true, renderTextFilters: ['br'] },
|
{ withNodes: true, renderTextFilters: ['br'] },
|
||||||
)
|
)
|
||||||
: oldLang(PREMIUM_FEATURE_DESCRIPTIONS[promo.videoSections[i]]), ['br'],
|
: oldLang(PREMIUM_FEATURE_DESCRIPTIONS[section]), ['br'],
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -35,7 +35,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.down {
|
.down {
|
||||||
--y-static: 3%;
|
--y-static: 2%;
|
||||||
--y-dynamic: 10%;
|
--y-dynamic: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,3 +67,16 @@
|
|||||||
bottom: initial;
|
bottom: initial;
|
||||||
border-radius: 10% 10% 0 0;
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@ -16,47 +16,55 @@ import styles from './PremiumFeaturePreviewVideo.module.scss';
|
|||||||
import DeviceFrame from '../../../../assets/premium/DeviceFrame.svg';
|
import DeviceFrame from '../../../../assets/premium/DeviceFrame.svg';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
videoId: string;
|
videoId?: string;
|
||||||
isReverseAnimation: boolean;
|
videoThumbnail?: ApiThumbnail;
|
||||||
isDown: boolean;
|
isActive?: boolean;
|
||||||
videoThumbnail: ApiThumbnail;
|
isReverseAnimation?: boolean;
|
||||||
index: number;
|
isDown?: boolean;
|
||||||
isActive: boolean;
|
index?: number;
|
||||||
|
className?: string;
|
||||||
|
wrapperClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PremiumFeaturePreviewVideo: FC<OwnProps> = ({
|
const PremiumFeaturePreviewVideo: FC<OwnProps> = ({
|
||||||
videoId,
|
videoId,
|
||||||
|
videoThumbnail,
|
||||||
|
isActive,
|
||||||
isReverseAnimation,
|
isReverseAnimation,
|
||||||
isDown,
|
isDown,
|
||||||
videoThumbnail,
|
|
||||||
index,
|
index,
|
||||||
isActive,
|
className,
|
||||||
|
wrapperClassName,
|
||||||
}) => {
|
}) => {
|
||||||
const mediaData = useMedia(`document${videoId}`);
|
const mediaData = useMedia(videoId ? `document${videoId}` : undefined);
|
||||||
const thumbnailRef = useCanvasBlur(videoThumbnail.dataUri);
|
const thumbnailRef = useCanvasBlur(videoThumbnail?.dataUri);
|
||||||
const transitionClassNames = useMediaTransitionDeprecated(mediaData);
|
const transitionClassNames = useMediaTransitionDeprecated(mediaData);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={buildClassName(styles.root, className)}>
|
||||||
<div
|
<div
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
styles.wrapper,
|
styles.wrapper,
|
||||||
isReverseAnimation && styles.reverse,
|
isReverseAnimation && styles.reverse,
|
||||||
isDown && styles.down,
|
isDown && styles.down,
|
||||||
|
wrapperClassName,
|
||||||
)}
|
)}
|
||||||
id={`premium_feature_preview_video_${index}`}
|
id={index !== undefined ? `premium_feature_preview_video_${index}` : undefined}
|
||||||
>
|
>
|
||||||
<img src={DeviceFrame} alt="" className={styles.frame} draggable={false} />
|
<img src={DeviceFrame} alt="" className={styles.frame} draggable={false} />
|
||||||
<canvas ref={thumbnailRef} className={styles.video} />
|
{!videoId && <div className={styles.placeholder} />}
|
||||||
<OptimizedVideo
|
{videoThumbnail && <canvas ref={thumbnailRef} className={styles.video} />}
|
||||||
canPlay={isActive}
|
{videoId && (
|
||||||
className={buildClassName(styles.video, transitionClassNames)}
|
<OptimizedVideo
|
||||||
src={mediaData}
|
canPlay={Boolean(isActive)}
|
||||||
disablePictureInPicture
|
className={buildClassName(styles.video, transitionClassNames)}
|
||||||
playsInline
|
src={mediaData}
|
||||||
muted
|
disablePictureInPicture
|
||||||
loop
|
playsInline
|
||||||
/>
|
muted
|
||||||
|
loop
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import CollectibleInfoModal from './collectible/CollectibleInfoModal.async';
|
|||||||
import DeleteAccountModal from './deleteAccount/DeleteAccountModal.async';
|
import DeleteAccountModal from './deleteAccount/DeleteAccountModal.async';
|
||||||
import EmojiStatusAccessModal from './emojiStatusAccess/EmojiStatusAccessModal.async';
|
import EmojiStatusAccessModal from './emojiStatusAccess/EmojiStatusAccessModal.async';
|
||||||
import FrozenAccountModal from './frozenAccount/FrozenAccountModal.async';
|
import FrozenAccountModal from './frozenAccount/FrozenAccountModal.async';
|
||||||
|
import AboutStarGiftModal from './gift/AboutStarGiftModal.async';
|
||||||
import GiftAuctionAcquiredModal from './gift/auction/GiftAuctionAcquiredModal.async';
|
import GiftAuctionAcquiredModal from './gift/auction/GiftAuctionAcquiredModal.async';
|
||||||
import GiftAuctionBidModal from './gift/auction/GiftAuctionBidModal.async';
|
import GiftAuctionBidModal from './gift/auction/GiftAuctionBidModal.async';
|
||||||
import GiftAuctionChangeRecipientModal from './gift/auction/GiftAuctionChangeRecipientModal.async';
|
import GiftAuctionChangeRecipientModal from './gift/auction/GiftAuctionChangeRecipientModal.async';
|
||||||
@ -105,6 +106,7 @@ type ModalKey = keyof Pick<TabState,
|
|||||||
'giftAuctionChangeRecipientModal' |
|
'giftAuctionChangeRecipientModal' |
|
||||||
'giftAuctionAcquiredModal' |
|
'giftAuctionAcquiredModal' |
|
||||||
'starGiftPriceDecreaseInfoModal' |
|
'starGiftPriceDecreaseInfoModal' |
|
||||||
|
'aboutStarGiftModal' |
|
||||||
'monetizationVerificationModal' |
|
'monetizationVerificationModal' |
|
||||||
'giftWithdrawModal' |
|
'giftWithdrawModal' |
|
||||||
'preparedMessageModal' |
|
'preparedMessageModal' |
|
||||||
@ -177,6 +179,7 @@ const MODALS: ModalRegistry = {
|
|||||||
giftAuctionChangeRecipientModal: GiftAuctionChangeRecipientModal,
|
giftAuctionChangeRecipientModal: GiftAuctionChangeRecipientModal,
|
||||||
giftAuctionAcquiredModal: GiftAuctionAcquiredModal,
|
giftAuctionAcquiredModal: GiftAuctionAcquiredModal,
|
||||||
starGiftPriceDecreaseInfoModal: StarGiftPriceDecreaseInfoModal,
|
starGiftPriceDecreaseInfoModal: StarGiftPriceDecreaseInfoModal,
|
||||||
|
aboutStarGiftModal: AboutStarGiftModal,
|
||||||
monetizationVerificationModal: VerificationMonetizationModal,
|
monetizationVerificationModal: VerificationMonetizationModal,
|
||||||
giftWithdrawModal: GiftWithdrawModal,
|
giftWithdrawModal: GiftWithdrawModal,
|
||||||
giftStatusInfoModal: GiftStatusInfoModal,
|
giftStatusInfoModal: GiftStatusInfoModal,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { memo, type TeactNode } from '../../../lib/teact/teact';
|
import { memo, type TeactNode } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { IconName } from '../../../types/icons';
|
import type { IconName } from '../../../types/icons';
|
||||||
|
import type { OwnProps as ButtonProps } from '../../ui/Button';
|
||||||
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ type OwnProps = {
|
|||||||
footer?: TeactNode;
|
footer?: TeactNode;
|
||||||
buttonText?: TeactNode;
|
buttonText?: TeactNode;
|
||||||
hasBackdrop?: boolean;
|
hasBackdrop?: boolean;
|
||||||
|
absoluteCloseButtonColor?: ButtonProps['color'];
|
||||||
withSeparator?: boolean;
|
withSeparator?: boolean;
|
||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
onButtonClick?: NoneToVoidFunction;
|
onButtonClick?: NoneToVoidFunction;
|
||||||
@ -40,6 +42,7 @@ const TableAboutModal = ({
|
|||||||
footer,
|
footer,
|
||||||
buttonText,
|
buttonText,
|
||||||
hasBackdrop,
|
hasBackdrop,
|
||||||
|
absoluteCloseButtonColor,
|
||||||
withSeparator,
|
withSeparator,
|
||||||
contentClassName,
|
contentClassName,
|
||||||
onClose,
|
onClose,
|
||||||
@ -51,7 +54,7 @@ const TableAboutModal = ({
|
|||||||
className={buildClassName(styles.root, className)}
|
className={buildClassName(styles.root, className)}
|
||||||
contentClassName={buildClassName(styles.content, contentClassName)}
|
contentClassName={buildClassName(styles.content, contentClassName)}
|
||||||
hasAbsoluteCloseButton
|
hasAbsoluteCloseButton
|
||||||
absoluteCloseButtonColor={hasBackdrop ? 'translucent-white' : undefined}
|
absoluteCloseButtonColor={absoluteCloseButtonColor || (hasBackdrop ? 'translucent-white' : undefined)}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
>
|
>
|
||||||
{headerIconName && (
|
{headerIconName && (
|
||||||
|
|||||||
@ -30,6 +30,8 @@ type OwnProps = {
|
|||||||
contentClassName?: string;
|
contentClassName?: string;
|
||||||
tableClassName?: string;
|
tableClassName?: string;
|
||||||
hasBackdrop?: boolean;
|
hasBackdrop?: boolean;
|
||||||
|
closeButtonColor?: 'translucent' | 'translucent-white';
|
||||||
|
moreMenuItems?: TeactNode;
|
||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
onButtonClick?: NoneToVoidFunction;
|
onButtonClick?: NoneToVoidFunction;
|
||||||
withBalanceBar?: boolean;
|
withBalanceBar?: boolean;
|
||||||
@ -50,6 +52,8 @@ const TableInfoModal = ({
|
|||||||
contentClassName,
|
contentClassName,
|
||||||
tableClassName,
|
tableClassName,
|
||||||
hasBackdrop,
|
hasBackdrop,
|
||||||
|
closeButtonColor,
|
||||||
|
moreMenuItems,
|
||||||
onClose,
|
onClose,
|
||||||
onButtonClick,
|
onButtonClick,
|
||||||
withBalanceBar,
|
withBalanceBar,
|
||||||
@ -68,12 +72,13 @@ const TableInfoModal = ({
|
|||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
hasCloseButton={Boolean(title)}
|
hasCloseButton={Boolean(title)}
|
||||||
hasAbsoluteCloseButton={!title}
|
hasAbsoluteCloseButton={!title}
|
||||||
absoluteCloseButtonColor={hasBackdrop ? 'translucent-white' : undefined}
|
absoluteCloseButtonColor={closeButtonColor || (hasBackdrop ? 'translucent-white' : undefined)}
|
||||||
isSlim
|
isSlim
|
||||||
header={modalHeader}
|
header={modalHeader}
|
||||||
title={title}
|
title={title}
|
||||||
className={className}
|
className={className}
|
||||||
contentClassName={buildClassName(styles.content, contentClassName)}
|
contentClassName={buildClassName(styles.content, contentClassName)}
|
||||||
|
moreMenuItems={moreMenuItems}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
withBalanceBar={withBalanceBar}
|
withBalanceBar={withBalanceBar}
|
||||||
currencyInBalanceBar={currencyInBalanceBar}
|
currencyInBalanceBar={currencyInBalanceBar}
|
||||||
|
|||||||
14
src/components/modals/gift/AboutStarGiftModal.async.tsx
Normal file
14
src/components/modals/gift/AboutStarGiftModal.async.tsx
Normal file
@ -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 ? <AboutStarGiftModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AboutStarGiftModalAsync;
|
||||||
53
src/components/modals/gift/AboutStarGiftModal.module.scss
Normal file
53
src/components/modals/gift/AboutStarGiftModal.module.scss
Normal file
@ -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;
|
||||||
|
}
|
||||||
93
src/components/modals/gift/AboutStarGiftModal.tsx
Normal file
93
src/components/modals/gift/AboutStarGiftModal.tsx
Normal file
@ -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 (
|
||||||
|
<div className={styles.header}>
|
||||||
|
<div className={styles.videoPreviewWrapper}>
|
||||||
|
<Sparkles preset="progress" className={styles.sparkles} />
|
||||||
|
<PremiumFeaturePreviewVideo
|
||||||
|
videoId={renderingModal?.videoId}
|
||||||
|
videoThumbnail={renderingModal?.videoThumbnail}
|
||||||
|
isActive={isOpen}
|
||||||
|
isDown
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.title}>
|
||||||
|
{lang('StarGiftInfoTitle')}
|
||||||
|
</div>
|
||||||
|
<div className={styles.subtitle}>
|
||||||
|
{lang('StarGiftInfoSubtitle')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [lang, renderingModal, isOpen]);
|
||||||
|
|
||||||
|
const footer = useMemo(() => {
|
||||||
|
if (!isOpen) return undefined;
|
||||||
|
return (
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<Button
|
||||||
|
iconName="understood"
|
||||||
|
iconClassName={styles.understoodIcon}
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
{lang('ButtonUnderstood')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [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 (
|
||||||
|
<TableAboutModal
|
||||||
|
isOpen={isOpen}
|
||||||
|
contentClassName={styles.content}
|
||||||
|
header={header}
|
||||||
|
listItemData={listItemData}
|
||||||
|
footer={footer}
|
||||||
|
hasBackdrop={Boolean(renderingModal?.videoId)}
|
||||||
|
absoluteCloseButtonColor="translucent-white"
|
||||||
|
onClose={handleClose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(AboutStarGiftModal);
|
||||||
@ -25,6 +25,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.withBadge {
|
||||||
|
--_height: 17rem;
|
||||||
|
}
|
||||||
|
|
||||||
:global {
|
:global {
|
||||||
canvas {
|
canvas {
|
||||||
transform-origin: center 45%;
|
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 {
|
.subtitleBadge {
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
padding-block: 0.25rem;
|
padding-block: 0.25rem;
|
||||||
border-radius: 1rem;
|
|
||||||
|
|
||||||
line-height: 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;
|
transition: color 150ms ease-in, background-color 0.15s !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -97,6 +107,10 @@
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge + .title {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 1.375rem;
|
line-height: 1.375rem;
|
||||||
@ -106,7 +120,16 @@
|
|||||||
transition: color 150ms ease-in;
|
transition: color 150ms ease-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title, .subtitle {
|
.title, .subtitle, .badge {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
margin-top: auto;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|||||||
@ -35,6 +35,7 @@ type OwnProps = {
|
|||||||
backdropAttribute: ApiStarGiftAttributeBackdrop;
|
backdropAttribute: ApiStarGiftAttributeBackdrop;
|
||||||
patternAttribute: ApiStarGiftAttributePattern;
|
patternAttribute: ApiStarGiftAttributePattern;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
badge?: TeactNode;
|
||||||
subtitle?: TeactNode;
|
subtitle?: TeactNode;
|
||||||
subtitlePeer?: ApiPeer;
|
subtitlePeer?: ApiPeer;
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -50,6 +51,7 @@ const UniqueGiftHeader = ({
|
|||||||
backdropAttribute,
|
backdropAttribute,
|
||||||
patternAttribute,
|
patternAttribute,
|
||||||
title,
|
title,
|
||||||
|
badge,
|
||||||
subtitle,
|
subtitle,
|
||||||
subtitlePeer,
|
subtitlePeer,
|
||||||
className,
|
className,
|
||||||
@ -89,6 +91,7 @@ const UniqueGiftHeader = ({
|
|||||||
<div className={buildClassName(styles.root,
|
<div className={buildClassName(styles.root,
|
||||||
'interactive-gift',
|
'interactive-gift',
|
||||||
showManageButtons && styles.withManageButtons,
|
showManageButtons && styles.withManageButtons,
|
||||||
|
badge && styles.withBadge,
|
||||||
className)}
|
className)}
|
||||||
>
|
>
|
||||||
<Transition
|
<Transition
|
||||||
@ -108,6 +111,9 @@ const UniqueGiftHeader = ({
|
|||||||
onMouseLeave={!IS_TOUCH_ENV ? unmarkGiftHover : undefined}
|
onMouseLeave={!IS_TOUCH_ENV ? unmarkGiftHover : undefined}
|
||||||
/>
|
/>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
{Boolean(badge) && (
|
||||||
|
<div className={styles.badge}>{badge}</div>
|
||||||
|
)}
|
||||||
{title && <h1 className={styles.title}>{title}</h1>}
|
{title && <h1 className={styles.title}>{title}</h1>}
|
||||||
{Boolean(subtitle) && (
|
{Boolean(subtitle) && (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -80,3 +80,9 @@
|
|||||||
.starIcon {
|
.starIcon {
|
||||||
line-height: 1rem !important;
|
line-height: 1rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.learnMoreLink {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: white !important;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,16 +1,23 @@
|
|||||||
import { memo, useMemo } from '../../../../lib/teact/teact';
|
import {
|
||||||
|
memo, useEffect, useMemo, useRef, useState,
|
||||||
|
} from '../../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../../global';
|
import { getActions, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
import type { ApiStarGiftAuctionState } from '../../../../api/types';
|
import type { ApiStarGiftAuctionState } from '../../../../api/types';
|
||||||
import type { TabState } from '../../../../global/types';
|
import type { TabState } from '../../../../global/types';
|
||||||
|
|
||||||
|
import { TME_LINK_PREFIX } from '../../../../config';
|
||||||
import { selectTabState } from '../../../../global/selectors';
|
import { selectTabState } from '../../../../global/selectors';
|
||||||
|
import { copyTextToClipboard } from '../../../../util/clipboard';
|
||||||
import { formatCountdown, formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
import { formatCountdown, formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
||||||
import { HOUR } from '../../../../util/dates/units';
|
import { HOUR } from '../../../../util/dates/units';
|
||||||
import { formatStarsAsIcon } from '../../../../util/localization/format';
|
import { formatStarsAsIcon } from '../../../../util/localization/format';
|
||||||
import { getServerTime } from '../../../../util/serverTime';
|
import { getServerTime } from '../../../../util/serverTime';
|
||||||
import { getStickerFromGift } from '../../../common/helpers/gifts';
|
import {
|
||||||
|
getRandomGiftPreviewAttributes, getStickerFromGift, type GiftPreviewAttributes,
|
||||||
|
preloadGiftAttributeStickers } from '../../../common/helpers/gifts';
|
||||||
|
|
||||||
|
import useInterval from '../../../../hooks/schedulers/useInterval';
|
||||||
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
@ -18,13 +25,16 @@ import useLastCallback from '../../../../hooks/useLastCallback';
|
|||||||
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
|
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
|
||||||
import Button from '../../../ui/Button';
|
import Button from '../../../ui/Button';
|
||||||
import Link from '../../../ui/Link';
|
import Link from '../../../ui/Link';
|
||||||
|
import MenuItem from '../../../ui/MenuItem';
|
||||||
import TextTimer from '../../../ui/TextTimer';
|
import TextTimer from '../../../ui/TextTimer';
|
||||||
import TableInfoModal, { type TableData } from '../../common/TableInfoModal';
|
import TableInfoModal, { type TableData } from '../../common/TableInfoModal';
|
||||||
import GiftItemStar from '../GiftItemStar';
|
import GiftItemStar from '../GiftItemStar';
|
||||||
|
import UniqueGiftHeader from '../UniqueGiftHeader';
|
||||||
|
|
||||||
import styles from './GiftAuctionModal.module.scss';
|
import styles from './GiftAuctionModal.module.scss';
|
||||||
|
|
||||||
const TEXT_TIMER_THRESHOLD = 48 * HOUR;
|
const TEXT_TIMER_THRESHOLD = 48 * HOUR;
|
||||||
|
const PREVIEW_UPDATE_INTERVAL = 3000;
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
modal: TabState['giftAuctionModal'];
|
modal: TabState['giftAuctionModal'];
|
||||||
@ -40,9 +50,15 @@ const GiftAuctionModal = ({ modal, auctionState }: OwnProps & StateProps) => {
|
|||||||
setGiftModalSelectedGift,
|
setGiftModalSelectedGift,
|
||||||
openGiftAuctionInfoModal,
|
openGiftAuctionInfoModal,
|
||||||
openGiftAuctionAcquiredModal,
|
openGiftAuctionAcquiredModal,
|
||||||
|
openAboutStarGiftModal,
|
||||||
|
showNotification,
|
||||||
|
openChatWithDraft,
|
||||||
|
openUrl,
|
||||||
|
openGiftInMarket,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const isOpen = Boolean(modal?.isOpen);
|
const isOpen = Boolean(modal?.isOpen);
|
||||||
|
const renderingModal = useCurrentOrPrev(modal);
|
||||||
const renderingAuctionState = useCurrentOrPrev(auctionState);
|
const renderingAuctionState = useCurrentOrPrev(auctionState);
|
||||||
|
|
||||||
const gift = renderingAuctionState?.gift;
|
const gift = renderingAuctionState?.gift;
|
||||||
@ -50,14 +66,43 @@ const GiftAuctionModal = ({ modal, auctionState }: OwnProps & StateProps) => {
|
|||||||
const userState = renderingAuctionState?.userState;
|
const userState = renderingAuctionState?.userState;
|
||||||
const isFinished = state?.type === 'finished';
|
const isFinished = state?.type === 'finished';
|
||||||
|
|
||||||
|
const [previewAttributes, setPreviewAttributes] = useState<GiftPreviewAttributes | undefined>();
|
||||||
|
const shouldUseUniqueHeader = Boolean(gift && state && previewAttributes);
|
||||||
|
|
||||||
|
const uniqueHeaderRef = useRef<HTMLDivElement>();
|
||||||
const lang = useLang();
|
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 handleClose = useLastCallback(() => closeGiftAuctionModal());
|
||||||
|
|
||||||
const handleLearnMoreClick = useLastCallback(() => {
|
const handleLearnMoreClick = useLastCallback(() => {
|
||||||
openGiftAuctionInfoModal({});
|
openGiftAuctionInfoModal({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleLearnMoreAboutGiftsClick = useLastCallback(() => {
|
||||||
|
openAboutStarGiftModal({});
|
||||||
|
});
|
||||||
|
|
||||||
const handleItemsBoughtClick = useLastCallback(() => {
|
const handleItemsBoughtClick = useLastCallback(() => {
|
||||||
if (!gift) return;
|
if (!gift) return;
|
||||||
const giftSticker = getStickerFromGift(gift);
|
const giftSticker = getStickerFromGift(gift);
|
||||||
@ -70,8 +115,65 @@ const GiftAuctionModal = ({ modal, auctionState }: OwnProps & StateProps) => {
|
|||||||
setGiftModalSelectedGift({ gift });
|
setGiftModalSelectedGift({ gift });
|
||||||
});
|
});
|
||||||
|
|
||||||
const header = useMemo(() => {
|
const auctionLink = useMemo(() => {
|
||||||
if (!gift || !state) {
|
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 = (
|
||||||
|
<Link className={styles.learnMoreLink} isPrimary onClick={handleLearnMoreAboutGiftsClick}>
|
||||||
|
{lang('GiftAuctionLearnMoreAboutGifts')}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={uniqueHeaderRef}>
|
||||||
|
<UniqueGiftHeader
|
||||||
|
modelAttribute={previewAttributes!.model}
|
||||||
|
backdropAttribute={previewAttributes!.backdrop}
|
||||||
|
patternAttribute={previewAttributes!.pattern}
|
||||||
|
title={giftTitle}
|
||||||
|
badge={badge}
|
||||||
|
subtitle={subtitle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [shouldUseUniqueHeader, gift, isFinished, lang, previewAttributes, handleLearnMoreAboutGiftsClick]);
|
||||||
|
|
||||||
|
const regularHeader = useMemo(() => {
|
||||||
|
if (!gift || !state || shouldUseUniqueHeader) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +199,9 @@ const GiftAuctionModal = ({ modal, auctionState }: OwnProps & StateProps) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, [gift, state, isFinished, lang, handleLearnMoreClick]);
|
}, [gift, state, shouldUseUniqueHeader, isFinished, lang, handleLearnMoreClick]);
|
||||||
|
|
||||||
|
const header = uniqueHeader || regularHeader;
|
||||||
|
|
||||||
const modalData = useMemo(() => {
|
const modalData = useMemo(() => {
|
||||||
if (!gift || !state || !userState) {
|
if (!gift || !state || !userState) {
|
||||||
@ -148,6 +252,10 @@ const GiftAuctionModal = ({ modal, auctionState }: OwnProps & StateProps) => {
|
|||||||
const auctionTimeLeft = state.endDate - getServerTime();
|
const auctionTimeLeft = state.endDate - getServerTime();
|
||||||
const shouldUseTextTimer = auctionTimeLeft > 0 && auctionTimeLeft < TEXT_TIMER_THRESHOLD;
|
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 = (
|
const footer = (
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
{acquiredCount > 0 && (
|
{acquiredCount > 0 && (
|
||||||
@ -165,6 +273,40 @@ const GiftAuctionModal = ({ modal, auctionState }: OwnProps & StateProps) => {
|
|||||||
}, { pluralValue: acquiredCount, withNodes: true })}
|
}, { pluralValue: acquiredCount, withNodes: true })}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
{canBuyOnFragment && (
|
||||||
|
<Link className={styles.itemsBoughtLink} isPrimary onClick={handleOpenFragment}>
|
||||||
|
{lang('GiftAuctionForSaleOnFragment', {
|
||||||
|
count: giftSticker ? (
|
||||||
|
<>
|
||||||
|
{lang.number(state.fragmentListedCount!)}
|
||||||
|
<AnimatedIconFromSticker
|
||||||
|
className={styles.itemsBoughtSticker}
|
||||||
|
sticker={giftSticker}
|
||||||
|
size={20}
|
||||||
|
play={false}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : lang.number(state.fragmentListedCount!),
|
||||||
|
}, { withNodes: true })}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
{canBuyOnTelegram && (
|
||||||
|
<Link className={styles.itemsBoughtLink} isPrimary onClick={handleOpenTelegramMarket}>
|
||||||
|
{lang('GiftAuctionForSaleOnTelegram', {
|
||||||
|
count: giftSticker ? (
|
||||||
|
<>
|
||||||
|
{lang.number(state.listedCount!)}
|
||||||
|
<AnimatedIconFromSticker
|
||||||
|
className={styles.itemsBoughtSticker}
|
||||||
|
sticker={giftSticker}
|
||||||
|
size={20}
|
||||||
|
play={false}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : lang.number(state.listedCount!),
|
||||||
|
}, { withNodes: true })}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
noForcedUpperCase
|
noForcedUpperCase
|
||||||
className={styles.footerButton}
|
className={styles.footerButton}
|
||||||
@ -194,7 +336,26 @@ const GiftAuctionModal = ({ modal, auctionState }: OwnProps & StateProps) => {
|
|||||||
tableData,
|
tableData,
|
||||||
footer,
|
footer,
|
||||||
};
|
};
|
||||||
}, [gift, state, userState, isFinished, lang, handleJoinClick, handleItemsBoughtClick, handleClose]);
|
}, [gift, state, userState, isFinished, lang, handleJoinClick, handleItemsBoughtClick, handleClose,
|
||||||
|
handleOpenFragment, handleOpenTelegramMarket]);
|
||||||
|
|
||||||
|
const moreMenuItems = useMemo(() => {
|
||||||
|
if (!shouldUseUniqueHeader) return undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem icon="info" onClick={handleLearnMoreClick}>
|
||||||
|
{lang('GiftAuctionLearnMoreMenuItem')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon="link-badge" onClick={handleCopyLink}>
|
||||||
|
{lang('CopyLink')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon="forward" onClick={handleShareLink}>
|
||||||
|
{lang('Share')}
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}, [shouldUseUniqueHeader, lang, handleLearnMoreClick, handleCopyLink, handleShareLink]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableInfoModal
|
<TableInfoModal
|
||||||
@ -204,6 +365,8 @@ const GiftAuctionModal = ({ modal, auctionState }: OwnProps & StateProps) => {
|
|||||||
tableData={modalData?.tableData}
|
tableData={modalData?.tableData}
|
||||||
className={styles.modal}
|
className={styles.modal}
|
||||||
contentClassName={styles.modalContent}
|
contentClassName={styles.modalContent}
|
||||||
|
closeButtonColor={shouldUseUniqueHeader ? 'translucent-white' : undefined}
|
||||||
|
moreMenuItems={moreMenuItems}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -27,7 +27,6 @@ import { renderGiftOriginalInfo } from '../../../common/helpers/giftOriginalInfo
|
|||||||
import { getGiftAttributes, getStickerFromGift } from '../../../common/helpers/gifts';
|
import { getGiftAttributes, getStickerFromGift } from '../../../common/helpers/gifts';
|
||||||
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
||||||
|
|
||||||
import useContextMenuHandlers from '../../../../hooks/useContextMenuHandlers';
|
|
||||||
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||||
import useFlag from '../../../../hooks/useFlag';
|
import useFlag from '../../../../hooks/useFlag';
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
@ -45,7 +44,6 @@ import Button from '../../../ui/Button';
|
|||||||
import Checkbox from '../../../ui/Checkbox';
|
import Checkbox from '../../../ui/Checkbox';
|
||||||
import ConfirmDialog from '../../../ui/ConfirmDialog';
|
import ConfirmDialog from '../../../ui/ConfirmDialog';
|
||||||
import Link from '../../../ui/Link';
|
import Link from '../../../ui/Link';
|
||||||
import Menu from '../../../ui/Menu';
|
|
||||||
import TableInfoModal, { type TableData } from '../../common/TableInfoModal';
|
import TableInfoModal, { type TableData } from '../../common/TableInfoModal';
|
||||||
import UniqueGiftHeader from '../UniqueGiftHeader';
|
import UniqueGiftHeader from '../UniqueGiftHeader';
|
||||||
|
|
||||||
@ -108,16 +106,7 @@ const GiftInfoModal = ({
|
|||||||
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState<boolean>(false);
|
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState<boolean>(false);
|
||||||
const [shouldPayInTon, setShouldPayInTon] = useState<boolean>(false);
|
const [shouldPayInTon, setShouldPayInTon] = useState<boolean>(false);
|
||||||
|
|
||||||
const moreButtonRef = useRef<HTMLButtonElement>();
|
|
||||||
const menuRef = useRef<HTMLDivElement>();
|
|
||||||
const uniqueGiftHeaderRef = useRef<HTMLDivElement>();
|
const uniqueGiftHeaderRef = useRef<HTMLDivElement>();
|
||||||
const {
|
|
||||||
isContextMenuOpen,
|
|
||||||
contextMenuAnchor,
|
|
||||||
handleContextMenu,
|
|
||||||
handleContextMenuClose,
|
|
||||||
handleContextMenuHide,
|
|
||||||
} = useContextMenuHandlers(moreButtonRef);
|
|
||||||
|
|
||||||
const handleSymbolClick = useLastCallback(() => {
|
const handleSymbolClick = useLastCallback(() => {
|
||||||
if (!gift || !giftAttributes?.pattern) return;
|
if (!gift || !giftAttributes?.pattern) return;
|
||||||
@ -540,19 +529,6 @@ const GiftInfoModal = ({
|
|||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
|
||||||
ref={moreButtonRef}
|
|
||||||
className={styles.moreMenuButton}
|
|
||||||
round
|
|
||||||
color="translucent-white"
|
|
||||||
size="tiny"
|
|
||||||
iconName="more"
|
|
||||||
aria-haspopup="menu"
|
|
||||||
aria-label={lang('AriaMoreButton')}
|
|
||||||
onContextMenu={handleContextMenu}
|
|
||||||
onClick={handleContextMenu}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{Boolean(resellPrice?.amount) && (
|
{Boolean(resellPrice?.amount) && (
|
||||||
<div className={styles.giftResalePriceContainer}>
|
<div className={styles.giftResalePriceContainer}>
|
||||||
{resellPrice.currency === TON_CURRENCY_CODE
|
{resellPrice.currency === TON_CURRENCY_CODE
|
||||||
@ -881,38 +857,16 @@ const GiftInfoModal = ({
|
|||||||
isGiftUnique, saleDateInfo,
|
isGiftUnique, saleDateInfo,
|
||||||
canBuyGift, giftOwnerTitle, resellPrice, giftSubtitle,
|
canBuyGift, giftOwnerTitle, resellPrice, giftSubtitle,
|
||||||
releasedByPeer, handleSymbolClick, handleBackdropClick, handleModelClick,
|
releasedByPeer, handleSymbolClick, handleBackdropClick, handleModelClick,
|
||||||
handleContextMenu,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const getRootElement = useLastCallback(() => uniqueGiftHeaderRef.current);
|
const moreMenuItems = typeGift && (
|
||||||
const getTriggerElement = useLastCallback(() => moreButtonRef.current);
|
<GiftMenuItems
|
||||||
const getMenuElement = useLastCallback(() => menuRef.current);
|
peerId={renderingModal!.peerId!}
|
||||||
const getLayout = useLastCallback(() => ({ withPortal: true }));
|
gift={typeGift}
|
||||||
|
canManage={canManage}
|
||||||
const uniqueGiftContextMenu = contextMenuAnchor && typeGift && (
|
collectibleEmojiStatuses={collectibleEmojiStatuses}
|
||||||
<Menu
|
currentUserEmojiStatus={currentUserEmojiStatus}
|
||||||
ref={menuRef}
|
/>
|
||||||
isOpen={isContextMenuOpen}
|
|
||||||
anchor={contextMenuAnchor}
|
|
||||||
className="gift-context-menu with-menu-transitions"
|
|
||||||
autoClose
|
|
||||||
withPortal
|
|
||||||
onClose={handleContextMenuClose}
|
|
||||||
onCloseAnimationEnd={handleContextMenuHide}
|
|
||||||
positionX="right"
|
|
||||||
getTriggerElement={getTriggerElement}
|
|
||||||
getRootElement={getRootElement}
|
|
||||||
getMenuElement={getMenuElement}
|
|
||||||
getLayout={getLayout}
|
|
||||||
>
|
|
||||||
<GiftMenuItems
|
|
||||||
peerId={renderingModal!.peerId!}
|
|
||||||
gift={typeGift}
|
|
||||||
canManage={canManage}
|
|
||||||
collectibleEmojiStatuses={collectibleEmojiStatuses}
|
|
||||||
currentUserEmojiStatus={currentUserEmojiStatus}
|
|
||||||
/>
|
|
||||||
</Menu>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -926,12 +880,13 @@ const GiftInfoModal = ({
|
|||||||
footer={modalData?.footer}
|
footer={modalData?.footer}
|
||||||
className={styles.modal}
|
className={styles.modal}
|
||||||
contentClassName={styles.modalContent}
|
contentClassName={styles.modalContent}
|
||||||
|
closeButtonColor={isGiftUnique ? 'translucent-white' : undefined}
|
||||||
|
moreMenuItems={moreMenuItems}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
withBalanceBar={Boolean(canBuyGift)}
|
withBalanceBar={Boolean(canBuyGift)}
|
||||||
currencyInBalanceBar={confirmPrice?.currency}
|
currencyInBalanceBar={confirmPrice?.currency}
|
||||||
isLowStackPriority
|
isLowStackPriority
|
||||||
/>
|
/>
|
||||||
{uniqueGiftContextMenu}
|
|
||||||
{uniqueGift && currentUser && Boolean(confirmPrice) && (
|
{uniqueGift && currentUser && Boolean(confirmPrice) && (
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
isOpen={isConfirmModalOpen}
|
isOpen={isConfirmModalOpen}
|
||||||
|
|||||||
@ -3,18 +3,14 @@ import {
|
|||||||
} from '../../../../lib/teact/teact';
|
} from '../../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../../global';
|
import { getActions, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
import type {
|
import type { ApiPeer } from '../../../../api/types';
|
||||||
ApiPeer,
|
|
||||||
ApiStarGiftAttributeModel,
|
|
||||||
} from '../../../../api/types';
|
|
||||||
import type { TabState } from '../../../../global/types';
|
import type { TabState } from '../../../../global/types';
|
||||||
import { ApiMediaFormat } from '../../../../api/types';
|
|
||||||
|
|
||||||
import { getStickerMediaHash } from '../../../../global/helpers';
|
|
||||||
import { getPeerTitle } from '../../../../global/helpers/peers';
|
import { getPeerTitle } from '../../../../global/helpers/peers';
|
||||||
import { selectPeer } from '../../../../global/selectors';
|
import { selectPeer } from '../../../../global/selectors';
|
||||||
import { fetch } from '../../../../util/mediaLoader';
|
import {
|
||||||
import { getRandomGiftPreviewAttributes, type GiftPreviewAttributes } from '../../../common/helpers/gifts';
|
getRandomGiftPreviewAttributes, type GiftPreviewAttributes,
|
||||||
|
preloadGiftAttributeStickers } from '../../../common/helpers/gifts';
|
||||||
|
|
||||||
import useInterval from '../../../../hooks/schedulers/useInterval';
|
import useInterval from '../../../../hooks/schedulers/useInterval';
|
||||||
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||||
@ -125,19 +121,10 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
|
|||||||
}
|
}
|
||||||
}, [isOpen, renderingModal?.sampleAttributes]);
|
}, [isOpen, renderingModal?.sampleAttributes]);
|
||||||
|
|
||||||
// Preload stickers and patterns
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const attributes = renderingModal?.sampleAttributes;
|
const attributes = renderingModal?.sampleAttributes;
|
||||||
if (!attributes) return;
|
if (!attributes) return;
|
||||||
const patternStickers = attributes.filter((attr): attr is ApiStarGiftAttributeModel => attr.type === 'pattern')
|
preloadGiftAttributeStickers(attributes);
|
||||||
.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);
|
|
||||||
});
|
|
||||||
}, [renderingModal?.sampleAttributes]);
|
}, [renderingModal?.sampleAttributes]);
|
||||||
|
|
||||||
const formattedPriceElement = useMemo(() => (upgradeStars ? (
|
const formattedPriceElement = useMemo(() => (upgradeStars ? (
|
||||||
|
|||||||
@ -248,4 +248,11 @@
|
|||||||
top: 0.875rem;
|
top: 0.875rem;
|
||||||
left: 0.875rem;
|
left: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-more-button {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 3;
|
||||||
|
top: 0.875rem;
|
||||||
|
right: 0.875rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { ElementRef, FC, TeactNode } from '../../lib/teact/teact';
|
import type { ElementRef, FC, TeactNode } from '../../lib/teact/teact';
|
||||||
import type React from '../../lib/teact/teact';
|
import type React from '../../lib/teact/teact';
|
||||||
import { beginHeavyAnimation, useEffect } from '../../lib/teact/teact';
|
import { beginHeavyAnimation, useEffect, useRef } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { TextPart } from '../../types';
|
import type { TextPart } from '../../types';
|
||||||
|
|
||||||
@ -9,6 +9,7 @@ import captureKeyboardListeners from '../../util/captureKeyboardListeners';
|
|||||||
import { disableDirectTextInput, enableDirectTextInput } from '../../util/directInputManager';
|
import { disableDirectTextInput, enableDirectTextInput } from '../../util/directInputManager';
|
||||||
import trapFocus from '../../util/trapFocus';
|
import trapFocus from '../../util/trapFocus';
|
||||||
|
|
||||||
|
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
||||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps';
|
import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps';
|
||||||
@ -16,6 +17,7 @@ import useOldLang from '../../hooks/useOldLang';
|
|||||||
import useShowTransition from '../../hooks/useShowTransition';
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
|
|
||||||
import Button, { type OwnProps as ButtonProps } from './Button';
|
import Button, { type OwnProps as ButtonProps } from './Button';
|
||||||
|
import Menu from './Menu';
|
||||||
import ModalStarBalanceBar from './ModalStarBalanceBar';
|
import ModalStarBalanceBar from './ModalStarBalanceBar';
|
||||||
import Portal from './Portal';
|
import Portal from './Portal';
|
||||||
|
|
||||||
@ -44,6 +46,7 @@ export type OwnProps = {
|
|||||||
isLowStackPriority?: boolean;
|
isLowStackPriority?: boolean;
|
||||||
dialogContent?: React.ReactNode;
|
dialogContent?: React.ReactNode;
|
||||||
ignoreFreeze?: boolean;
|
ignoreFreeze?: boolean;
|
||||||
|
moreMenuItems?: TeactNode;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onCloseAnimationEnd?: () => void;
|
onCloseAnimationEnd?: () => void;
|
||||||
onEnter?: () => void;
|
onEnter?: () => void;
|
||||||
@ -72,6 +75,7 @@ const Modal: FC<OwnProps> = ({
|
|||||||
isLowStackPriority,
|
isLowStackPriority,
|
||||||
dialogContent,
|
dialogContent,
|
||||||
dialogClassName,
|
dialogClassName,
|
||||||
|
moreMenuItems,
|
||||||
onClose,
|
onClose,
|
||||||
onCloseAnimationEnd,
|
onCloseAnimationEnd,
|
||||||
onEnter,
|
onEnter,
|
||||||
@ -88,6 +92,25 @@ const Modal: FC<OwnProps> = ({
|
|||||||
withShouldRender: true,
|
withShouldRender: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const localDialogRef = useRef<HTMLDivElement>();
|
||||||
|
const moreButtonRef = useRef<HTMLButtonElement>();
|
||||||
|
const menuRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
const {
|
||||||
|
isContextMenuOpen,
|
||||||
|
contextMenuAnchor,
|
||||||
|
handleContextMenu,
|
||||||
|
handleContextMenuClose,
|
||||||
|
handleContextMenuHide,
|
||||||
|
} = useContextMenuHandlers(moreButtonRef);
|
||||||
|
|
||||||
|
const actualDialogRef = dialogRef || localDialogRef;
|
||||||
|
|
||||||
|
const getRootElement = useLastCallback(() => actualDialogRef.current);
|
||||||
|
const getTriggerElement = useLastCallback(() => moreButtonRef.current);
|
||||||
|
const getMenuElement = useLastCallback(() => menuRef.current);
|
||||||
|
const getLayout = useLastCallback(() => ({ withPortal: true }));
|
||||||
|
|
||||||
const withCloseButton = hasCloseButton || hasAbsoluteCloseButton;
|
const withCloseButton = hasCloseButton || hasAbsoluteCloseButton;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -193,8 +216,39 @@ const Modal: FC<OwnProps> = ({
|
|||||||
)}
|
)}
|
||||||
<div className="modal-container">
|
<div className="modal-container">
|
||||||
<div className="modal-backdrop" onClick={!noBackdropClose ? onClose : undefined} />
|
<div className="modal-backdrop" onClick={!noBackdropClose ? onClose : undefined} />
|
||||||
<div className={modalDialogClassName} ref={dialogRef} style={dialogStyle}>
|
<div className={modalDialogClassName} ref={actualDialogRef} style={dialogStyle}>
|
||||||
{renderHeader()}
|
{renderHeader()}
|
||||||
|
{Boolean(moreMenuItems) && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
ref={moreButtonRef}
|
||||||
|
className="modal-more-button"
|
||||||
|
round
|
||||||
|
color={absoluteCloseButtonColor}
|
||||||
|
size="tiny"
|
||||||
|
iconName="more"
|
||||||
|
ariaLabel={lang('AriaMoreButton')}
|
||||||
|
onClick={handleContextMenu}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
|
/>
|
||||||
|
<Menu
|
||||||
|
ref={menuRef}
|
||||||
|
isOpen={isContextMenuOpen}
|
||||||
|
anchor={contextMenuAnchor}
|
||||||
|
autoClose
|
||||||
|
withPortal
|
||||||
|
positionX="right"
|
||||||
|
onClose={handleContextMenuClose}
|
||||||
|
onCloseAnimationEnd={handleContextMenuHide}
|
||||||
|
getRootElement={getRootElement}
|
||||||
|
getTriggerElement={getTriggerElement}
|
||||||
|
getMenuElement={getMenuElement}
|
||||||
|
getLayout={getLayout}
|
||||||
|
>
|
||||||
|
{moreMenuItems}
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{dialogContent}
|
{dialogContent}
|
||||||
<div className={buildClassName('modal-content custom-scroll', contentClassName)} style={style}>
|
<div className={buildClassName('modal-content custom-scroll', contentClassName)} style={style}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -583,11 +583,17 @@ addActionHandler('shiftGiftUpgradeNextPrice', async (global, _actions, payload):
|
|||||||
addActionHandler('openGiftAuctionModal', async (global, _actions, payload): Promise<void> => {
|
addActionHandler('openGiftAuctionModal', async (global, _actions, payload): Promise<void> => {
|
||||||
const { gift, tabId = getCurrentTabId() } = payload;
|
const { gift, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
await getPromiseActions().loadActiveGiftAuction({ giftId: gift.id, tabId });
|
const [, preview] = await Promise.all([
|
||||||
|
getPromiseActions().loadActiveGiftAuction({ giftId: gift.id, tabId }),
|
||||||
|
callApi('fetchStarGiftUpgradePreview', { giftId: gift.id }),
|
||||||
|
]);
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
giftAuctionModal: { isOpen: true },
|
giftAuctionModal: {
|
||||||
|
isOpen: true,
|
||||||
|
sampleAttributes: preview?.sampleAttributes,
|
||||||
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -42,6 +42,7 @@ addActionHandler('processOpenChatOrThread', (global, actions, payload): ActionRe
|
|||||||
actions.closeStarsBalanceModal({ tabId });
|
actions.closeStarsBalanceModal({ tabId });
|
||||||
actions.closeStarsTransactionModal({ tabId });
|
actions.closeStarsTransactionModal({ tabId });
|
||||||
actions.closeGiftInfoModal({ tabId });
|
actions.closeGiftInfoModal({ tabId });
|
||||||
|
actions.closeGiftAuctionModal({ tabId });
|
||||||
|
|
||||||
if (!currentMessageList || (
|
if (!currentMessageList || (
|
||||||
currentMessageList.chatId !== chatId
|
currentMessageList.chatId !== chatId
|
||||||
|
|||||||
@ -441,6 +441,32 @@ addActionHandler('openGiftAuctionInfoModal', (global, _actions, payload): Action
|
|||||||
|
|
||||||
addTabStateResetterAction('closeGiftAuctionInfoModal', 'giftAuctionInfoModal');
|
addTabStateResetterAction('closeGiftAuctionInfoModal', 'giftAuctionInfoModal');
|
||||||
|
|
||||||
|
addActionHandler('openAboutStarGiftModal', async (global, _actions, payload): Promise<void> => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
const result = await callApi('fetchPremiumPromo');
|
||||||
|
|
||||||
|
let videoId: string | undefined;
|
||||||
|
let videoThumbnail;
|
||||||
|
|
||||||
|
if (result?.promo) {
|
||||||
|
const giftsIndex = result.promo.videoSections.indexOf('gifts');
|
||||||
|
if (giftsIndex !== -1 && giftsIndex < result.promo.videos.length) {
|
||||||
|
const video = result.promo.videos[giftsIndex];
|
||||||
|
videoId = video.id;
|
||||||
|
videoThumbnail = video.thumbnail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateTabState(global, {
|
||||||
|
aboutStarGiftModal: { isOpen: true, videoId, videoThumbnail },
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addTabStateResetterAction('closeAboutStarGiftModal', 'aboutStarGiftModal');
|
||||||
|
|
||||||
addActionHandler('openGiftAuctionChangeRecipientModal', (global, _actions, payload): ActionReturnType => {
|
addActionHandler('openGiftAuctionChangeRecipientModal', (global, _actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
oldPeerId, newPeerId, message, shouldHideName, tabId = getCurrentTabId(),
|
oldPeerId, newPeerId, message, shouldHideName, tabId = getCurrentTabId(),
|
||||||
|
|||||||
@ -2709,6 +2709,8 @@ export interface ActionPayloads {
|
|||||||
closeGiftAuctionBidModal: WithTabId | undefined;
|
closeGiftAuctionBidModal: WithTabId | undefined;
|
||||||
openGiftAuctionInfoModal: WithTabId | undefined;
|
openGiftAuctionInfoModal: WithTabId | undefined;
|
||||||
closeGiftAuctionInfoModal: WithTabId | undefined;
|
closeGiftAuctionInfoModal: WithTabId | undefined;
|
||||||
|
openAboutStarGiftModal: WithTabId | undefined;
|
||||||
|
closeAboutStarGiftModal: WithTabId | undefined;
|
||||||
openGiftAuctionChangeRecipientModal: {
|
openGiftAuctionChangeRecipientModal: {
|
||||||
oldPeerId: string;
|
oldPeerId: string;
|
||||||
newPeerId: string;
|
newPeerId: string;
|
||||||
|
|||||||
@ -53,6 +53,7 @@ import type {
|
|||||||
ApiStarsTransaction,
|
ApiStarsTransaction,
|
||||||
ApiStarTopupOption,
|
ApiStarTopupOption,
|
||||||
ApiSticker,
|
ApiSticker,
|
||||||
|
ApiThumbnail,
|
||||||
ApiTypeCurrencyAmount,
|
ApiTypeCurrencyAmount,
|
||||||
ApiTypePrepaidGiveaway,
|
ApiTypePrepaidGiveaway,
|
||||||
ApiTypeStoryView,
|
ApiTypeStoryView,
|
||||||
@ -895,6 +896,7 @@ export type TabState = {
|
|||||||
|
|
||||||
giftAuctionModal?: {
|
giftAuctionModal?: {
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
|
sampleAttributes?: ApiStarGiftAttribute[];
|
||||||
};
|
};
|
||||||
|
|
||||||
giftAuctionBidModal?: {
|
giftAuctionBidModal?: {
|
||||||
@ -908,6 +910,12 @@ export type TabState = {
|
|||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
aboutStarGiftModal?: {
|
||||||
|
isOpen?: boolean;
|
||||||
|
videoId?: string;
|
||||||
|
videoThumbnail?: ApiThumbnail;
|
||||||
|
};
|
||||||
|
|
||||||
giftAuctionChangeRecipientModal?: {
|
giftAuctionChangeRecipientModal?: {
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
oldPeerId?: string;
|
oldPeerId?: string;
|
||||||
|
|||||||
16
src/types/language.d.ts
vendored
16
src/types/language.d.ts
vendored
@ -1830,6 +1830,16 @@ export interface LangPair {
|
|||||||
'GiftAuctionChangeRecipientTitle': undefined;
|
'GiftAuctionChangeRecipientTitle': undefined;
|
||||||
'GiftAuctionAveragePrice': undefined;
|
'GiftAuctionAveragePrice': undefined;
|
||||||
'GiftAuctionTapToBidMore': undefined;
|
'GiftAuctionTapToBidMore': undefined;
|
||||||
|
'GiftAuctionLearnMoreAboutGifts': undefined;
|
||||||
|
'GiftAuctionLearnMoreMenuItem': undefined;
|
||||||
|
'StarGiftInfoTitle': undefined;
|
||||||
|
'StarGiftInfoSubtitle': undefined;
|
||||||
|
'StarGiftInfoUniqueTitle': undefined;
|
||||||
|
'StarGiftInfoUniqueSubtitle': undefined;
|
||||||
|
'StarGiftInfoTradableTitle': undefined;
|
||||||
|
'StarGiftInfoTradableSubtitle': undefined;
|
||||||
|
'StarGiftInfoWearableTitle': undefined;
|
||||||
|
'StarGiftInfoWearableSubtitle': undefined;
|
||||||
'StarGift': undefined;
|
'StarGift': undefined;
|
||||||
'SettingsItemPrivacyPasskeys': undefined;
|
'SettingsItemPrivacyPasskeys': undefined;
|
||||||
'SettingsItemPrivacyOn': undefined;
|
'SettingsItemPrivacyOn': undefined;
|
||||||
@ -3145,6 +3155,12 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'RatingLevel': {
|
'RatingLevel': {
|
||||||
'level': V;
|
'level': V;
|
||||||
};
|
};
|
||||||
|
'GiftAuctionForSaleOnFragment': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'GiftAuctionForSaleOnTelegram': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
'GiftLockedMessage': {
|
'GiftLockedMessage': {
|
||||||
'relativeDate': V;
|
'relativeDate': V;
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user