Gifts: Support isPremiumRequired (#6100)
This commit is contained in:
parent
fd57d088f9
commit
6d39fd28d8
@ -25,7 +25,7 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
|||||||
if (starGift instanceof GramJs.StarGiftUnique) {
|
if (starGift instanceof GramJs.StarGiftUnique) {
|
||||||
const {
|
const {
|
||||||
id, num, ownerId, ownerName, title, attributes, availabilityIssued, availabilityTotal, slug, ownerAddress,
|
id, num, ownerId, ownerName, title, attributes, availabilityIssued, availabilityTotal, slug, ownerAddress,
|
||||||
giftAddress, resellAmount, releasedBy, resaleTonOnly,
|
giftAddress, resellAmount, releasedBy, resaleTonOnly, requirePremium,
|
||||||
} = starGift;
|
} = starGift;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -43,6 +43,7 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
|||||||
giftAddress,
|
giftAddress,
|
||||||
resellPrice: resellAmount && resellAmount.map((amount) => buildApiCurrencyAmount(amount)).filter(Boolean),
|
resellPrice: resellAmount && resellAmount.map((amount) => buildApiCurrencyAmount(amount)).filter(Boolean),
|
||||||
releasedByPeerId: releasedBy && getApiChatIdFromMtpPeer(releasedBy),
|
releasedByPeerId: releasedBy && getApiChatIdFromMtpPeer(releasedBy),
|
||||||
|
requirePremium,
|
||||||
resaleTonOnly,
|
resaleTonOnly,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -50,6 +51,7 @@ 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,
|
||||||
} = starGift;
|
} = starGift;
|
||||||
|
|
||||||
addDocumentToLocalDb(starGift.sticker);
|
addDocumentToLocalDb(starGift.sticker);
|
||||||
@ -74,6 +76,10 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
|||||||
resellMinStars: resellMinStars?.toJSNumber(),
|
resellMinStars: resellMinStars?.toJSNumber(),
|
||||||
releasedByPeerId: releasedBy && getApiChatIdFromMtpPeer(releasedBy),
|
releasedByPeerId: releasedBy && getApiChatIdFromMtpPeer(releasedBy),
|
||||||
availabilityResale: availabilityResale?.toJSNumber(),
|
availabilityResale: availabilityResale?.toJSNumber(),
|
||||||
|
requirePremium,
|
||||||
|
limitedPerUser,
|
||||||
|
perUserTotal,
|
||||||
|
perUserRemains,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,10 @@ export interface ApiStarGiftRegular {
|
|||||||
resellMinStars?: number;
|
resellMinStars?: number;
|
||||||
releasedByPeerId?: string;
|
releasedByPeerId?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
requirePremium?: true;
|
||||||
|
limitedPerUser?: true;
|
||||||
|
perUserTotal?: number;
|
||||||
|
perUserRemains?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiStarGiftUnique {
|
export interface ApiStarGiftUnique {
|
||||||
@ -39,6 +43,7 @@ export interface ApiStarGiftUnique {
|
|||||||
giftAddress?: string;
|
giftAddress?: string;
|
||||||
resellPrice?: ApiTypeCurrencyAmount[];
|
resellPrice?: ApiTypeCurrencyAmount[];
|
||||||
releasedByPeerId?: string;
|
releasedByPeerId?: string;
|
||||||
|
requirePremium?: true;
|
||||||
resaleTonOnly?: true;
|
resaleTonOnly?: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2153,6 +2153,10 @@
|
|||||||
"TitleAgeCheckFailed" = "Age Check Failed";
|
"TitleAgeCheckFailed" = "Age Check Failed";
|
||||||
"TitleAgeCheckSuccess" = "Age Check Success";
|
"TitleAgeCheckSuccess" = "Age Check Success";
|
||||||
"ButtonAgeVerification" = "Verify My Age";
|
"ButtonAgeVerification" = "Verify My Age";
|
||||||
|
"GiftRibbonPremium" = "premium";
|
||||||
|
"NotificationGiftsLimit" = "You've already sent {count} of these gifts, and it's the limit.";
|
||||||
|
"PremiumGiftHeader" = "Premium Gift";
|
||||||
|
"DescriptionGiftPremiumRequired" = "Subscribe to **Telegram Premium** to send up to **{count}** of these gifts and unlock access to multiple additional features.";
|
||||||
"PriceInStars" = "Price in Stars";
|
"PriceInStars" = "Price in Stars";
|
||||||
"PriceInTON" = "Price in TON";
|
"PriceInTON" = "Price in TON";
|
||||||
"DescriptionComposerGiftMinimumCurrencyPrice" = "Minimum price is **{amount}**.";
|
"DescriptionComposerGiftMinimumCurrencyPrice" = "Minimum price is **{amount}**.";
|
||||||
@ -2164,4 +2168,5 @@
|
|||||||
"LabelPayInTON" = "Pay in TON";
|
"LabelPayInTON" = "Pay in TON";
|
||||||
"PriceChanged" = "Price Changed";
|
"PriceChanged" = "Price Changed";
|
||||||
"PayNewPrice" = "Pay New Price";
|
"PayNewPrice" = "Pay New Price";
|
||||||
"PriceChangedText" = "The price has already changed from **{originalAmount}** to **{newAmount}**. Do you want to pay the new price?";
|
"PriceChangedText" = "The price has already changed from **{originalAmount}** to **{newAmount}**. Do you want to pay the new price?";
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ const COLORS = {
|
|||||||
blue: [['#6ED2FF', '#34A4FC'], ['#344F5A', '#152E42']],
|
blue: [['#6ED2FF', '#34A4FC'], ['#344F5A', '#152E42']],
|
||||||
purple: [['#E367D7', '#757BF6'], ['#E367D7', '#757BF6']],
|
purple: [['#E367D7', '#757BF6'], ['#E367D7', '#757BF6']],
|
||||||
green: [['#52D553', '#4BB121'], ['#52D553', '#4BB121']],
|
green: [['#52D553', '#4BB121'], ['#52D553', '#4BB121']],
|
||||||
|
orange: [['#D48F23', '#BE7E15'], ['#D48F23', '#BE7E15']],
|
||||||
} as const;
|
} as const;
|
||||||
type ColorKey = keyof typeof COLORS;
|
type ColorKey = keyof typeof COLORS;
|
||||||
|
|
||||||
|
|||||||
@ -50,6 +50,11 @@
|
|||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sticker-wrapper {
|
||||||
|
position: relative;
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.header-text {
|
.header-text {
|
||||||
margin-inline: 0.5rem;
|
margin-inline: 0.5rem;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
@ -176,3 +181,16 @@
|
|||||||
.subscriptionOption {
|
.subscriptionOption {
|
||||||
margin: 0.8125rem;
|
margin: 0.8125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.starParticlesHeader,
|
||||||
|
.giftParticlesHeader {
|
||||||
|
position: relative !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.starParticlesHeader {
|
||||||
|
padding-top: 10rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.giftParticlesHeader {
|
||||||
|
padding-top: 11rem !important;
|
||||||
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import type {
|
|||||||
ApiPremiumPromo,
|
ApiPremiumPromo,
|
||||||
ApiPremiumSection,
|
ApiPremiumSection,
|
||||||
ApiPremiumSubscriptionOption,
|
ApiPremiumSubscriptionOption,
|
||||||
|
ApiStarGift,
|
||||||
ApiSticker,
|
ApiSticker,
|
||||||
ApiStickerSet,
|
ApiStickerSet,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
@ -19,6 +20,7 @@ import { selectIsCurrentUserPremium, selectStickerSet, selectTabState, selectUse
|
|||||||
import { selectPremiumLimit } from '../../../global/selectors/limits';
|
import { selectPremiumLimit } from '../../../global/selectors/limits';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { formatCurrency } from '../../../util/formatCurrency';
|
import { formatCurrency } from '../../../util/formatCurrency';
|
||||||
|
import { getStickerFromGift } from '../../common/helpers/gifts';
|
||||||
import { REM } from '../../common/helpers/mediaDimensions';
|
import { REM } from '../../common/helpers/mediaDimensions';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
||||||
@ -99,6 +101,7 @@ type StateProps = {
|
|||||||
isSuccess?: boolean;
|
isSuccess?: boolean;
|
||||||
isGift?: boolean;
|
isGift?: boolean;
|
||||||
monthsAmount?: number;
|
monthsAmount?: number;
|
||||||
|
gift?: ApiStarGift;
|
||||||
limitChannels: number;
|
limitChannels: number;
|
||||||
limitPins: number;
|
limitPins: number;
|
||||||
limitLinks: number;
|
limitLinks: number;
|
||||||
@ -130,6 +133,7 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
|||||||
toUser,
|
toUser,
|
||||||
monthsAmount,
|
monthsAmount,
|
||||||
premiumPromoOrder,
|
premiumPromoOrder,
|
||||||
|
gift,
|
||||||
}) => {
|
}) => {
|
||||||
const dialogRef = useRef<HTMLDivElement>();
|
const dialogRef = useRef<HTMLDivElement>();
|
||||||
const {
|
const {
|
||||||
@ -273,6 +277,10 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
|||||||
if (!promo || (fromUserStatusEmoji && !fromUserStatusSet)) return undefined;
|
if (!promo || (fromUserStatusEmoji && !fromUserStatusSet)) return undefined;
|
||||||
|
|
||||||
function getHeaderText() {
|
function getHeaderText() {
|
||||||
|
if (gift) {
|
||||||
|
return lang('PremiumGiftHeader');
|
||||||
|
}
|
||||||
|
|
||||||
if (isGift) {
|
if (isGift) {
|
||||||
return renderText(
|
return renderText(
|
||||||
fromUser?.id === currentUserId
|
fromUser?.id === currentUserId
|
||||||
@ -307,6 +315,11 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getHeaderDescription() {
|
function getHeaderDescription() {
|
||||||
|
if (gift) {
|
||||||
|
const perUserTotal = gift.type !== 'starGiftUnique' ? gift.perUserTotal : 0;
|
||||||
|
return lang('DescriptionGiftPremiumRequired', { count: perUserTotal });
|
||||||
|
}
|
||||||
|
|
||||||
if (isGift) {
|
if (isGift) {
|
||||||
return fromUser?.id === currentUserId
|
return fromUser?.id === currentUserId
|
||||||
? oldLang('TelegramPremiumUserGiftedPremiumOutboundDialogSubtitle', getUserFullName(toUser))
|
? oldLang('TelegramPremiumUserGiftedPremiumOutboundDialogSubtitle', getUserFullName(toUser))
|
||||||
@ -322,6 +335,52 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
|||||||
: oldLang(isPremium ? 'TelegramPremiumSubscribedSubtitle' : 'TelegramPremiumSubtitle');
|
: oldLang(isPremium ? 'TelegramPremiumSubscribedSubtitle' : 'TelegramPremiumSubtitle');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderHeader() {
|
||||||
|
if (gift) {
|
||||||
|
const giftSticker = getStickerFromGift(gift);
|
||||||
|
return (
|
||||||
|
<ParticlesHeader
|
||||||
|
model="sticker"
|
||||||
|
sticker={giftSticker}
|
||||||
|
color="purple"
|
||||||
|
title={getHeaderText()}
|
||||||
|
description={renderText(getHeaderDescription(), ['simple_markdown', 'emoji'])}
|
||||||
|
className={styles.giftParticlesHeader}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fromUserStatusEmoji) {
|
||||||
|
return (
|
||||||
|
<ParticlesHeader
|
||||||
|
model="swaying-star"
|
||||||
|
color="purple"
|
||||||
|
title={getHeaderText()}
|
||||||
|
description={renderText(getHeaderDescription(), ['simple_markdown', 'emoji'])}
|
||||||
|
className={styles.starParticlesHeader}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CustomEmoji
|
||||||
|
className={styles.statusEmoji}
|
||||||
|
onClick={handleOpenStatusSet}
|
||||||
|
documentId={fromUserStatusEmoji.id}
|
||||||
|
isBig
|
||||||
|
size={STATUS_EMOJI_SIZE}
|
||||||
|
/>
|
||||||
|
<h2 className={buildClassName(styles.headerText, fromUserStatusSet && styles.stickerSetText)}>
|
||||||
|
{getHeaderText()}
|
||||||
|
</h2>
|
||||||
|
<div className={styles.description}>
|
||||||
|
{renderText(getHeaderDescription(), ['simple_markdown', 'emoji'])}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function renderFooterText() {
|
function renderFooterText() {
|
||||||
if (!promo || (isGift && fromUser?.id === currentUserId)) {
|
if (!promo || (isGift && fromUser?.id === currentUserId)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -375,30 +434,7 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
|||||||
>
|
>
|
||||||
<Icon name="close" />
|
<Icon name="close" />
|
||||||
</Button>
|
</Button>
|
||||||
{!fromUserStatusEmoji ? (
|
{renderHeader()}
|
||||||
<ParticlesHeader
|
|
||||||
model="swaying-star"
|
|
||||||
color="purple"
|
|
||||||
title={getHeaderText()}
|
|
||||||
description={renderText(getHeaderDescription(), ['simple_markdown', 'emoji'])}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<CustomEmoji
|
|
||||||
className={styles.statusEmoji}
|
|
||||||
onClick={handleOpenStatusSet}
|
|
||||||
documentId={fromUserStatusEmoji.id}
|
|
||||||
isBig
|
|
||||||
size={STATUS_EMOJI_SIZE}
|
|
||||||
/>
|
|
||||||
<h2 className={buildClassName(styles.headerText, fromUserStatusSet && styles.stickerSetText)}>
|
|
||||||
{getHeaderText()}
|
|
||||||
</h2>
|
|
||||||
<div className={styles.description}>
|
|
||||||
{renderText(getHeaderDescription(), ['simple_markdown', 'emoji'])}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{!isPremium && !isGift && renderSubscriptionOptions()}
|
{!isPremium && !isGift && renderSubscriptionOptions()}
|
||||||
<div className={buildClassName(styles.header, isHeaderHidden && styles.hiddenHeader)}>
|
<div className={buildClassName(styles.header, isHeaderHidden && styles.hiddenHeader)}>
|
||||||
<h2 className={styles.premiumHeaderText}>
|
<h2 className={styles.premiumHeaderText}>
|
||||||
@ -483,6 +519,7 @@ export default memo(withGlobal<OwnProps>((global): StateProps => {
|
|||||||
isSuccess: premiumModal?.isSuccess,
|
isSuccess: premiumModal?.isSuccess,
|
||||||
isGift: premiumModal?.isGift,
|
isGift: premiumModal?.isGift,
|
||||||
monthsAmount: premiumModal?.monthsAmount,
|
monthsAmount: premiumModal?.monthsAmount,
|
||||||
|
gift: premiumModal?.gift,
|
||||||
fromUser,
|
fromUser,
|
||||||
fromUserStatusEmoji,
|
fromUserStatusEmoji,
|
||||||
fromUserStatusSet,
|
fromUserStatusSet,
|
||||||
|
|||||||
@ -30,3 +30,14 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
text-wrap: balance;
|
text-wrap: balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stickerWrapper {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: 2rem;
|
||||||
|
transition: transform 0.25s ease-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,35 +1,47 @@
|
|||||||
import type { TeactNode } from '@teact';
|
import type { TeactNode } from '@teact';
|
||||||
import { memo, useLayoutEffect, useRef } from '@teact';
|
import { memo, useLayoutEffect, useRef } from '@teact';
|
||||||
|
|
||||||
|
import type { ApiSticker } from '../../../api/types';
|
||||||
|
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { PARTICLE_BURST_PARAMS, PARTICLE_COLORS, setupParticles } from '../../../util/particles.ts';
|
import { PARTICLE_BURST_PARAMS, PARTICLE_COLORS, setupParticles } from '../../../util/particles.ts';
|
||||||
|
import { REM } from '../../common/helpers/mediaDimensions';
|
||||||
|
|
||||||
import useLastCallback from '../../../hooks/useLastCallback.ts';
|
import useLastCallback from '../../../hooks/useLastCallback.ts';
|
||||||
|
|
||||||
|
import StickerView from '../../common/StickerView';
|
||||||
import SpeedingDiamond from './SpeedingDiamond.tsx';
|
import SpeedingDiamond from './SpeedingDiamond.tsx';
|
||||||
import SwayingStar from './SwayingStar.tsx';
|
import SwayingStar from './SwayingStar.tsx';
|
||||||
|
|
||||||
import styles from './ParticlesHeader.module.scss';
|
import styles from './ParticlesHeader.module.scss';
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
model: 'swaying-star' | 'speeding-diamond';
|
model: 'swaying-star' | 'speeding-diamond' | 'sticker';
|
||||||
|
sticker?: ApiSticker;
|
||||||
color: 'purple' | 'gold' | 'blue';
|
color: 'purple' | 'gold' | 'blue';
|
||||||
title: TeactNode;
|
title: TeactNode;
|
||||||
description: TeactNode;
|
description: TeactNode;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GIFT_STICKER_SIZE = 8 * REM;
|
||||||
|
|
||||||
const PARTICLE_PARAMS = {
|
const PARTICLE_PARAMS = {
|
||||||
centerShift: [0, -36] as const,
|
centerShift: [0, -36] as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ParticlesHeader({
|
function ParticlesHeader({
|
||||||
model,
|
model,
|
||||||
|
sticker,
|
||||||
color,
|
color,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
|
className,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const canvasRef = useRef<HTMLCanvasElement>();
|
const canvasRef = useRef<HTMLCanvasElement>();
|
||||||
|
const stickerRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (isDisabled) return undefined;
|
if (isDisabled) return undefined;
|
||||||
@ -49,7 +61,7 @@ function ParticlesHeader({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={buildClassName(styles.root, className)}>
|
||||||
<canvas ref={canvasRef} className={styles.particles} />
|
<canvas ref={canvasRef} className={styles.particles} />
|
||||||
|
|
||||||
{model === 'swaying-star' ? (
|
{model === 'swaying-star' ? (
|
||||||
@ -58,8 +70,23 @@ function ParticlesHeader({
|
|||||||
centerShift={PARTICLE_PARAMS.centerShift}
|
centerShift={PARTICLE_PARAMS.centerShift}
|
||||||
onMouseMove={handleMouseMove}
|
onMouseMove={handleMouseMove}
|
||||||
/>
|
/>
|
||||||
) : model === 'speeding-diamond' && (
|
) : model === 'speeding-diamond' ? (
|
||||||
<SpeedingDiamond onMouseMove={handleMouseMove} />
|
<SpeedingDiamond onMouseMove={handleMouseMove} />
|
||||||
|
) : model === 'sticker' && sticker && (
|
||||||
|
<div
|
||||||
|
ref={stickerRef}
|
||||||
|
className={styles.stickerWrapper}
|
||||||
|
style={`width: ${GIFT_STICKER_SIZE}px; height: ${GIFT_STICKER_SIZE}px`}
|
||||||
|
onMouseMove={handleMouseMove}
|
||||||
|
>
|
||||||
|
<StickerView
|
||||||
|
containerRef={stickerRef}
|
||||||
|
sticker={sticker}
|
||||||
|
size={GIFT_STICKER_SIZE}
|
||||||
|
shouldPreloadPreview
|
||||||
|
shouldLoop={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<h2 className={styles.title}>
|
<h2 className={styles.title}>
|
||||||
|
|||||||
@ -75,6 +75,16 @@
|
|||||||
font-size: 0.75rem !important;
|
font-size: 0.75rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.premiumRequired {
|
||||||
|
outline: 0.125rem solid #D18D21;
|
||||||
|
outline-offset: -0.125rem;
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 0.125rem solid #D18D21;
|
||||||
|
outline-offset: -0.125rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.amount {
|
.amount {
|
||||||
margin-top: 0.0625rem; // It just refuses to be centered
|
margin-top: 0.0625rem; // It just refuses to be centered
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { memo, useMemo, useRef, useState } from '../../../lib/teact/teact';
|
import { memo, useMemo, useRef, useState } from '../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiStarGift,
|
ApiStarGift,
|
||||||
@ -7,6 +7,7 @@ import type {
|
|||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
|
||||||
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../config';
|
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../config';
|
||||||
|
import { selectIsCurrentUserPremium } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { formatStarsAsIcon, formatTonAsIcon } from '../../../util/localization/format';
|
import { formatStarsAsIcon, formatTonAsIcon } from '../../../util/localization/format';
|
||||||
import { getStickerFromGift } from '../../common/helpers/gifts';
|
import { getStickerFromGift } from '../../common/helpers/gifts';
|
||||||
@ -30,12 +31,16 @@ export type OwnProps = {
|
|||||||
isResale?: boolean;
|
isResale?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
isCurrentUserPremium?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const GIFT_STICKER_SIZE = 90;
|
const GIFT_STICKER_SIZE = 90;
|
||||||
|
|
||||||
function GiftItemStar({
|
function GiftItemStar({
|
||||||
gift, observeIntersection, onClick, isResale,
|
gift, observeIntersection, onClick, isResale, isCurrentUserPremium,
|
||||||
}: OwnProps) {
|
}: OwnProps & StateProps) {
|
||||||
const { openGiftInfoModal } = getActions();
|
const { openGiftInfoModal, openPremiumModal, showNotification } = getActions();
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>();
|
const ref = useRef<HTMLDivElement>();
|
||||||
const stickerRef = useRef<HTMLDivElement>();
|
const stickerRef = useRef<HTMLDivElement>();
|
||||||
@ -69,6 +74,9 @@ function GiftItemStar({
|
|||||||
? lang.number(resellMinStars) + '+' : priceInfo?.amount || 0;
|
? lang.number(resellMinStars) + '+' : priceInfo?.amount || 0;
|
||||||
const isLimited = !isGiftUnique && Boolean(regularGift?.isLimited);
|
const isLimited = !isGiftUnique && Boolean(regularGift?.isLimited);
|
||||||
const isSoldOut = !isGiftUnique && Boolean(regularGift?.isSoldOut);
|
const isSoldOut = !isGiftUnique && Boolean(regularGift?.isSoldOut);
|
||||||
|
const isPremiumRequired = Boolean(gift?.requirePremium);
|
||||||
|
const isUserLimitReached = Boolean(regularGift?.limitedPerUser && !regularGift?.perUserRemains);
|
||||||
|
const perUserTotal = regularGift?.perUserTotal;
|
||||||
|
|
||||||
const handleGiftClick = useLastCallback(() => {
|
const handleGiftClick = useLastCallback(() => {
|
||||||
if (isSoldOut && !isResale) {
|
if (isSoldOut && !isResale) {
|
||||||
@ -76,6 +84,25 @@ function GiftItemStar({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isUserLimitReached) {
|
||||||
|
showNotification({
|
||||||
|
message: lang('NotificationGiftsLimit', {
|
||||||
|
count: perUserTotal,
|
||||||
|
}, {
|
||||||
|
withMarkdown: true,
|
||||||
|
withNodes: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPremiumRequired && !isCurrentUserPremium) {
|
||||||
|
openPremiumModal({
|
||||||
|
gift,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
onClick(gift, isResale ? 'resell' : 'original');
|
onClick(gift, isResale ? 'resell' : 'original');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -116,6 +143,9 @@ function GiftItemStar({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (isPremiumRequired) {
|
||||||
|
return <GiftRibbon color="orange" text={lang('LimitPremium')} />;
|
||||||
|
}
|
||||||
if (isResale) {
|
if (isResale) {
|
||||||
return <GiftRibbon color="green" text={lang('GiftRibbonResale')} />;
|
return <GiftRibbon color="green" text={lang('GiftRibbonResale')} />;
|
||||||
}
|
}
|
||||||
@ -126,7 +156,7 @@ function GiftItemStar({
|
|||||||
return <GiftRibbon color="blue" text={lang('GiftLimited')} />;
|
return <GiftRibbon color="blue" text={lang('GiftLimited')} />;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [isGiftUnique, isResale, gift, isSoldOut, isLimited, lang, giftNumber]);
|
}, [isGiftUnique, isResale, gift, isSoldOut, isLimited, lang, giftNumber, isPremiumRequired]);
|
||||||
|
|
||||||
useOnIntersect(ref, observeIntersection, (entry) => {
|
useOnIntersect(ref, observeIntersection, (entry) => {
|
||||||
const visible = entry.isIntersecting;
|
const visible = entry.isIntersecting;
|
||||||
@ -136,7 +166,12 @@ function GiftItemStar({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={buildClassName(styles.container, styles.starGift, 'starGiftItem')}
|
className={buildClassName(
|
||||||
|
styles.container,
|
||||||
|
styles.starGift,
|
||||||
|
'starGiftItem',
|
||||||
|
isPremiumRequired && styles.premiumRequired,
|
||||||
|
)}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
onClick={handleGiftClick}
|
onClick={handleGiftClick}
|
||||||
@ -178,4 +213,12 @@ function GiftItemStar({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(GiftItemStar);
|
export default memo(
|
||||||
|
withGlobal<OwnProps>((global): StateProps => {
|
||||||
|
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isCurrentUserPremium,
|
||||||
|
};
|
||||||
|
})(GiftItemStar),
|
||||||
|
);
|
||||||
|
|||||||
@ -484,7 +484,7 @@ addActionHandler('closePremiumModal', (global, actions, payload): ActionReturnTy
|
|||||||
|
|
||||||
addActionHandler('openPremiumModal', async (global, actions, payload): Promise<void> => {
|
addActionHandler('openPremiumModal', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
initialSection, fromUserId, isSuccess, isGift, monthsAmount, toUserId,
|
initialSection, fromUserId, isSuccess, isGift, monthsAmount, toUserId, gift,
|
||||||
tabId = getCurrentTabId(),
|
tabId = getCurrentTabId(),
|
||||||
} = payload || {};
|
} = payload || {};
|
||||||
|
|
||||||
@ -505,6 +505,7 @@ addActionHandler('openPremiumModal', async (global, actions, payload): Promise<v
|
|||||||
isGift,
|
isGift,
|
||||||
monthsAmount,
|
monthsAmount,
|
||||||
isSuccess,
|
isSuccess,
|
||||||
|
gift,
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|||||||
@ -2435,6 +2435,7 @@ export interface ActionPayloads {
|
|||||||
isSuccess?: boolean;
|
isSuccess?: boolean;
|
||||||
isGift?: boolean;
|
isGift?: boolean;
|
||||||
monthsAmount?: number;
|
monthsAmount?: number;
|
||||||
|
gift?: ApiStarGift;
|
||||||
} & WithTabId) | undefined;
|
} & WithTabId) | undefined;
|
||||||
closePremiumModal: WithTabId | undefined;
|
closePremiumModal: WithTabId | undefined;
|
||||||
|
|
||||||
|
|||||||
@ -640,6 +640,7 @@ export type TabState = {
|
|||||||
isGift?: boolean;
|
isGift?: boolean;
|
||||||
monthsAmount?: number;
|
monthsAmount?: number;
|
||||||
isSuccess?: boolean;
|
isSuccess?: boolean;
|
||||||
|
gift?: ApiStarGift;
|
||||||
};
|
};
|
||||||
|
|
||||||
giveawayModal?: {
|
giveawayModal?: {
|
||||||
|
|||||||
8
src/types/language.d.ts
vendored
8
src/types/language.d.ts
vendored
@ -1608,6 +1608,8 @@ export interface LangPair {
|
|||||||
'TitleAgeCheckFailed': undefined;
|
'TitleAgeCheckFailed': undefined;
|
||||||
'TitleAgeCheckSuccess': undefined;
|
'TitleAgeCheckSuccess': undefined;
|
||||||
'ButtonAgeVerification': undefined;
|
'ButtonAgeVerification': undefined;
|
||||||
|
'GiftRibbonPremium': undefined;
|
||||||
|
'PremiumGiftHeader': undefined;
|
||||||
'PriceInStars': undefined;
|
'PriceInStars': undefined;
|
||||||
'PriceInTON': undefined;
|
'PriceInTON': undefined;
|
||||||
'OnlyAcceptTON': undefined;
|
'OnlyAcceptTON': undefined;
|
||||||
@ -2793,6 +2795,12 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'ButtonSensitiveAlways': {
|
'ButtonSensitiveAlways': {
|
||||||
'years': V;
|
'years': V;
|
||||||
};
|
};
|
||||||
|
'NotificationGiftsLimit': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'DescriptionGiftPremiumRequired': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
'DescriptionComposerGiftMinimumCurrencyPrice': {
|
'DescriptionComposerGiftMinimumCurrencyPrice': {
|
||||||
'amount': V;
|
'amount': V;
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user