Premium Promo: Add missing sections & bugfixes (#4321)
This commit is contained in:
parent
aea5598e55
commit
f85fe40c7e
@ -2,7 +2,7 @@
|
||||
import BigInt from 'big-integer';
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type { ApiLimitType } from '../../../global/types';
|
||||
import type { ApiLimitType, ApiPremiumSection } from '../../../global/types';
|
||||
import type { ApiAppConfig } from '../../types';
|
||||
|
||||
import {
|
||||
@ -107,7 +107,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
||||
maxUniqueReactions: appConfig.reactions_uniq_max,
|
||||
premiumBotUsername: appConfig.premium_bot_username,
|
||||
premiumInvoiceSlug: appConfig.premium_invoice_slug,
|
||||
premiumPromoOrder: appConfig.premium_promo_order,
|
||||
premiumPromoOrder: appConfig.premium_promo_order as ApiPremiumSection[],
|
||||
isPremiumPurchaseBlocked: appConfig.premium_purchase_blocked,
|
||||
defaultEmojiStatusesStickerSetId: appConfig.default_emoji_statuses_stickerset_id,
|
||||
topicsPinnedLimit: appConfig.topics_pinned_limit,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type { ApiPremiumSection } from '../../../global/types';
|
||||
import type {
|
||||
ApiBoostsStatus,
|
||||
ApiCheckedGiftCode,
|
||||
@ -181,7 +182,7 @@ export function buildApiPremiumPromo(promo: GramJs.help.PremiumPromo): ApiPremiu
|
||||
return {
|
||||
statusText,
|
||||
statusEntities: statusEntities.map(buildApiMessageEntity),
|
||||
videoSections,
|
||||
videoSections: videoSections as ApiPremiumSection[],
|
||||
videos: videos.map(buildApiDocument).filter(Boolean),
|
||||
options: periodOptions.map(buildApiPremiumSubscriptionOption),
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ApiLimitType, CallbackAction } from '../../global/types';
|
||||
import type { ApiLimitType, ApiPremiumSection, CallbackAction } from '../../global/types';
|
||||
import type { ApiDocument, ApiPhoto, ApiReaction } from './messages';
|
||||
import type { ApiUser } from './users';
|
||||
|
||||
@ -185,7 +185,7 @@ export interface ApiAppConfig {
|
||||
premiumInvoiceSlug: string;
|
||||
premiumBotUsername: string;
|
||||
isPremiumPurchaseBlocked: boolean;
|
||||
premiumPromoOrder: string[];
|
||||
premiumPromoOrder: ApiPremiumSection[];
|
||||
defaultEmojiStatusesStickerSetId: string;
|
||||
maxUniqueReactions: number;
|
||||
topicsPinnedLimit: number;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ApiPremiumSection } from '../../global/types';
|
||||
import type { ApiInvoiceContainer } from '../../types';
|
||||
import type { ApiWebDocument } from './bots';
|
||||
import type { ApiDocument, ApiMessageEntity, ApiPaymentCredentials } from './messages';
|
||||
@ -64,7 +65,7 @@ export interface ApiReceipt {
|
||||
}
|
||||
|
||||
export interface ApiPremiumPromo {
|
||||
videoSections: string[];
|
||||
videoSections: ApiPremiumSection[];
|
||||
videos: ApiDocument[];
|
||||
statusText: string;
|
||||
statusEntities: ApiMessageEntity[];
|
||||
|
||||
3
src/assets/premium/PremiumLastSeen.svg
Normal file
3
src/assets/premium/PremiumLastSeen.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.0383 19.7139C21.7257 18.9733 18.8699 16.9701 18.0434 16.8556C17.5312 16.7846 16.415 18.2067 16.1413 19.8246C15.9733 20.8174 16.2209 21.81 16.8961 22.4129C18.6717 23.9983 22.351 20.4546 22.0383 19.7139ZM26.3572 7.22842C24.5742 6.62499 20.3579 8.74054 19.1894 14.9306C19.1894 15.2314 21.6203 16.9738 23.2952 17.5755C24.1503 17.8827 27.2917 14.6979 27.9631 11.5094C28.363 9.60974 27.2686 7.53689 26.3572 7.22842ZM11.6516 21.8097C11.1613 21.3904 7.50032 20.3232 6.79835 20.6606C6.09639 20.998 5.8308 25.3063 8.4321 25.8826C11.0334 26.4588 12.142 22.229 11.6516 21.8097ZM7.3738 18.2653C7.8641 18.6845 11.6074 19.9798 12.3094 19.6424C13.0114 19.3049 17.4904 10.042 12.9081 9.07884C11.4585 8.77415 8.62607 10.6618 7.77039 13.1382C6.93613 15.5526 7.13175 18.0583 7.3738 18.2653Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 943 B |
3
src/assets/premium/PremiumMessagePrivacy.svg
Normal file
3
src/assets/premium/PremiumMessagePrivacy.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 13.4V23.1373C6 24.3489 6 24.9547 6.23959 25.2352C6.44749 25.4786 6.75934 25.6078 7.07846 25.5827C7.44624 25.5538 7.87462 25.1254 8.73137 24.2686L10.0627 22.9373C10.4086 22.5914 10.5816 22.4184 10.7834 22.2947C10.9624 22.1851 11.1575 22.1043 11.3615 22.0553C11.5917 22 11.8363 22 12.3255 22H19.6C21.8402 22 22.9603 22 23.816 21.564C24.5686 21.1805 25.1805 20.5686 25.564 19.816C26 18.9603 26 17.8402 26 15.6V13.4C26 11.1598 26 10.0397 25.564 9.18404C25.1805 8.43139 24.5686 7.81947 23.816 7.43597C22.9603 7 21.8402 7 19.6 7H12.4C10.1598 7 9.03969 7 8.18404 7.43597C7.43139 7.81947 6.81947 8.43139 6.43597 9.18404C6 10.0397 6 11.1598 6 13.4ZM17.335 12.0025C17.335 11.2646 16.7363 10.6666 15.9983 10.6675C15.2617 10.6685 14.665 11.2659 14.665 12.0025V13.0003C14.7696 13 14.881 13 15 13H17C17.119 13 17.2304 13 17.335 13.0003V12.0025ZM13.335 12.0025V13.1152C13.3006 13.1264 13.2672 13.1387 13.2346 13.1522C12.7446 13.3552 12.3552 13.7446 12.1522 14.2346C12 14.6022 12 15.0681 12 16C12 16.9319 12 17.3978 12.1522 17.7654C12.3552 18.2554 12.7446 18.6448 13.2346 18.8478C13.6022 19 14.0681 19 15 19H17C17.9319 19 18.3978 19 18.7654 18.8478C19.2554 18.6448 19.6448 18.2554 19.8478 17.7654C20 17.3978 20 16.9319 20 16C20 15.0681 20 14.6022 19.8478 14.2346C19.6448 13.7446 19.2554 13.3552 18.7654 13.1522C18.7328 13.1387 18.6994 13.1264 18.665 13.1152V12.0025C18.665 10.5294 17.4698 9.33567 15.9966 9.33753C14.5261 9.33938 13.335 10.532 13.335 12.0025Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@ -10,6 +10,7 @@ import { LOCAL_TGS_URLS } from './helpers/animatedAssets';
|
||||
import renderText from './helpers/renderText';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
import Modal, { ANIMATION_DURATION } from '../ui/Modal';
|
||||
@ -36,33 +37,36 @@ const ReadDateModal = ({ isOpen, user }: OwnProps & StateProps) => {
|
||||
} = getActions();
|
||||
const userName = getUserFirstOrLastName(user);
|
||||
|
||||
const handleShowReadTime = () => {
|
||||
const handleShowReadTime = useLastCallback(() => {
|
||||
updateGlobalPrivacySettings({ shouldHideReadMarks: false });
|
||||
closeGetReadDateModal();
|
||||
|
||||
setTimeout(() => {
|
||||
showNotification({ message: lang('PremiumReadSet') });
|
||||
}, CLOSE_ANIMATION_DURATION);
|
||||
};
|
||||
});
|
||||
|
||||
const handleOpenPremium = () => {
|
||||
const handleOpenPremium = useLastCallback(() => {
|
||||
closeGetReadDateModal();
|
||||
|
||||
setTimeout(() => {
|
||||
openPremiumModal();
|
||||
}, CLOSE_ANIMATION_DURATION);
|
||||
};
|
||||
});
|
||||
|
||||
const handleClose = useLastCallback(() => {
|
||||
closeGetReadDateModal();
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal isSlim isOpen={isOpen} onClose={closeGetReadDateModal}>
|
||||
<Modal isSlim isOpen={isOpen} onClose={handleClose}>
|
||||
<div className={styles.container} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<Button
|
||||
className={styles.closeButton}
|
||||
color="translucent"
|
||||
round
|
||||
size="smaller"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => closeGetReadDateModal()}
|
||||
onClick={handleClose}
|
||||
ariaLabel="Close"
|
||||
>
|
||||
<Icon name="close" />
|
||||
@ -78,7 +82,6 @@ const ReadDateModal = ({ isOpen, user }: OwnProps & StateProps) => {
|
||||
<p className={styles.desc}>{renderText(lang('PremiumReadText1', userName), ['simple_markdown'])}</p>
|
||||
<Button
|
||||
size="smaller"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={handleShowReadTime}
|
||||
className={styles.button}
|
||||
>
|
||||
@ -87,7 +90,6 @@ const ReadDateModal = ({ isOpen, user }: OwnProps & StateProps) => {
|
||||
<Separator className={styles.separator}>{lang('PremiumOr')}</Separator>
|
||||
<h2 className={styles.header}>{lang('PremiumReadHeader2')}</h2>
|
||||
<p className={styles.desc}>{renderText(lang('PremiumReadText2', userName), ['simple_markdown'])}</p>
|
||||
{/* eslint-disable-next-line react/jsx-no-bind */}
|
||||
<Button withPremiumGradient size="smaller" onClick={handleOpenPremium} className={styles.button}>
|
||||
{lang('PremiumLastSeenButton2')}
|
||||
</Button>
|
||||
|
||||
@ -66,7 +66,7 @@ const SliderDots: FC<OwnProps> = ({
|
||||
styles.dot,
|
||||
index === active && styles.active,
|
||||
(isPreLast || isPreFirst) && styles.medium,
|
||||
(isLast || isFirst) && styles.small,
|
||||
(isLast || isFirst || isInvisible) && styles.small,
|
||||
isInvisible && styles.invisible,
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -129,7 +129,6 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
|
||||
)}
|
||||
|
||||
<ListItem
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={handleDownloadLog}
|
||||
icon="bug"
|
||||
>
|
||||
|
||||
@ -33,6 +33,12 @@ const SettingsPrivacyLastSeen = ({
|
||||
(isEnabled) => updateGlobalPrivacySettings({ shouldHideReadMarks: isEnabled }),
|
||||
);
|
||||
|
||||
const handleOpenPremiumModal = useLastCallback(() => {
|
||||
openPremiumModal({
|
||||
initialSection: 'last_seen',
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{canShowHideReadTime && (
|
||||
@ -50,8 +56,7 @@ const SettingsPrivacyLastSeen = ({
|
||||
<div className="settings-item">
|
||||
<ListItem
|
||||
leftElement={<PremiumIcon className="icon" withGradient big />}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => openPremiumModal()}
|
||||
onClick={handleOpenPremiumModal}
|
||||
>
|
||||
{isCurrentUserPremium ? lang('PrivacyLastSeenPremiumForPremium') : lang('PrivacyLastSeenPremium')}
|
||||
</ListItem>
|
||||
|
||||
@ -358,7 +358,6 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
||||
className="settings-folders-list-item mb-0"
|
||||
icon="link"
|
||||
multiline
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={handleEditInviteClick}
|
||||
clickArg={invite.url}
|
||||
>
|
||||
|
||||
@ -591,7 +591,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
||||
<AttachBotInstallModal bot={attachBotToInstall} />
|
||||
<AttachBotRecipientPicker requestedAttachBotInChat={requestedAttachBotInChat} />
|
||||
<MessageListHistoryHandler />
|
||||
{isPremiumModalOpen && <PremiumMainModal isOpen={isPremiumModalOpen} />}
|
||||
<PremiumMainModal isOpen={isPremiumModalOpen} />
|
||||
<PremiumLimitReachedModal limit={limitReached} />
|
||||
<PaymentModal isOpen={isPaymentModalOpen} onClose={closePaymentModal} />
|
||||
<ReceiptModal isOpen={isReceiptModalOpen} onClose={clearReceipt} />
|
||||
|
||||
@ -41,8 +41,6 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
isOpen,
|
||||
user,
|
||||
gifts,
|
||||
monthlyCurrency,
|
||||
monthlyAmount,
|
||||
}) => {
|
||||
const { openPremiumModal, closeGiftPremiumModal, openUrl } = getActions();
|
||||
|
||||
@ -56,14 +54,12 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cheaperGift = renderedGifts.reduce((acc, gift) => {
|
||||
return gift.amount < firstGift?.amount ? gift : firstGift;
|
||||
const basicGift = renderedGifts.reduce((acc, gift) => {
|
||||
return gift.months < firstGift.months ? gift : firstGift;
|
||||
}, firstGift);
|
||||
|
||||
return cheaperGift.currency === monthlyCurrency && monthlyAmount
|
||||
? monthlyAmount
|
||||
: Math.floor(cheaperGift.amount / cheaperGift.months);
|
||||
}, [firstGift, renderedGifts, monthlyAmount, monthlyCurrency]);
|
||||
return Math.floor(basicGift.amount / basicGift.months);
|
||||
}, [firstGift, renderedGifts]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
@ -165,14 +161,12 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
const { forUserId, monthlyCurrency, monthlyAmount } = selectTabState(global).giftPremiumModal || {};
|
||||
const { forUserId } = selectTabState(global).giftPremiumModal || {};
|
||||
const user = forUserId ? selectUser(global, forUserId) : undefined;
|
||||
const gifts = user ? selectUserFullInfo(global, user.id)?.premiumGifts : undefined;
|
||||
|
||||
return {
|
||||
user,
|
||||
gifts,
|
||||
monthlyCurrency,
|
||||
monthlyAmount: monthlyAmount ? Number(monthlyAmount) : undefined,
|
||||
};
|
||||
})(GiftPremiumModal));
|
||||
|
||||
@ -1,22 +1,24 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo } from '../../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { hexToRgb, lerpRgb } from '../../../util/switchTheme';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
|
||||
import styles from './PremiumFeatureItem.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
type OwnProps<T> = {
|
||||
icon: string;
|
||||
isFontIcon?: boolean;
|
||||
title: string;
|
||||
text: string;
|
||||
index: number;
|
||||
count: number;
|
||||
onClick?: VoidFunction;
|
||||
section: T;
|
||||
onClick?: (section: T) => void;
|
||||
};
|
||||
|
||||
const COLORS = [
|
||||
@ -24,22 +26,28 @@ const COLORS = [
|
||||
'#9873FF', '#768DFF', '#55A5FC', '#52B0C9', '#4FBC93', '#4CC663',
|
||||
].map(hexToRgb);
|
||||
|
||||
const PremiumFeatureItem: FC<OwnProps> = ({
|
||||
// eslint-disable-next-line @typescript-eslint/comma-dangle
|
||||
const PremiumFeatureItem = <T,>({
|
||||
icon,
|
||||
isFontIcon,
|
||||
title,
|
||||
text,
|
||||
index,
|
||||
count,
|
||||
section,
|
||||
onClick,
|
||||
}) => {
|
||||
}: OwnProps<T>) => {
|
||||
const newIndex = (index / count) * COLORS.length;
|
||||
const colorA = COLORS[Math.floor(newIndex)];
|
||||
const colorB = COLORS[Math.ceil(newIndex)] ?? colorA;
|
||||
const { r, g, b } = lerpRgb(colorA, colorB, 0.5);
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
onClick?.(section);
|
||||
});
|
||||
|
||||
return (
|
||||
<ListItem buttonClassName={styles.root} onClick={onClick} inactive={!onClick}>
|
||||
<ListItem buttonClassName={styles.root} onClick={handleClick} inactive={!onClick}>
|
||||
{isFontIcon ? (
|
||||
<i
|
||||
className={buildClassName(styles.fontIcon, `icon icon-${icon}`)}
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useCallback, useEffect, useMemo, useRef, useState,
|
||||
memo, useEffect, useMemo, useRef, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { toggleExtraClass } from '../../../lib/teact/teact-dom';
|
||||
|
||||
import type { ApiPremiumPromo } from '../../../api/types';
|
||||
import type { ApiLimitType, GlobalState } from '../../../global/types';
|
||||
import type { ApiPremiumPromo, ApiPremiumSubscriptionOption } from '../../../api/types';
|
||||
import type { ApiLimitTypeForPromo, ApiPremiumSection, GlobalState } from '../../../global/types';
|
||||
|
||||
import { PREMIUM_BOTTOM_VIDEOS, PREMIUM_FEATURE_SECTIONS, PREMIUM_LIMITS_ORDER } from '../../../config';
|
||||
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
||||
import animateHorizontalScroll from '../../../util/animateHorizontalScroll';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatCurrency } from '../../../util/formatCurrency';
|
||||
@ -13,6 +16,7 @@ import renderText from '../../common/helpers/renderText';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import usePrevious from '../../../hooks/usePrevious';
|
||||
|
||||
import SliderDots from '../../common/SliderDots';
|
||||
@ -24,7 +28,7 @@ import PremiumFeaturePreviewVideo from './previews/PremiumFeaturePreviewVideo';
|
||||
|
||||
import styles from './PremiumFeatureModal.module.scss';
|
||||
|
||||
export const PREMIUM_FEATURE_TITLES: Record<string, string> = {
|
||||
export const PREMIUM_FEATURE_TITLES: Record<ApiPremiumSection, string> = {
|
||||
double_limits: 'PremiumPreviewLimits',
|
||||
infinite_reactions: 'PremiumPreviewReactions2',
|
||||
premium_stickers: 'PremiumPreviewStickers',
|
||||
@ -40,9 +44,11 @@ export const PREMIUM_FEATURE_TITLES: Record<string, string> = {
|
||||
translations: 'PremiumPreviewTranslations',
|
||||
stories: 'PremiumPreviewStories',
|
||||
saved_tags: 'PremiumPreviewTags2',
|
||||
last_seen: 'PremiumPreviewLastSeen',
|
||||
message_privacy: 'PremiumPreviewMessagePrivacy',
|
||||
};
|
||||
|
||||
export const PREMIUM_FEATURE_DESCRIPTIONS: Record<string, string> = {
|
||||
export const PREMIUM_FEATURE_DESCRIPTIONS: Record<ApiPremiumSection, string> = {
|
||||
double_limits: 'PremiumPreviewLimitsDescription',
|
||||
infinite_reactions: 'PremiumPreviewReactions2Description',
|
||||
premium_stickers: 'PremiumPreviewStickersDescription',
|
||||
@ -58,56 +64,11 @@ export const PREMIUM_FEATURE_DESCRIPTIONS: Record<string, string> = {
|
||||
translations: 'PremiumPreviewTranslationsDescription',
|
||||
stories: 'PremiumPreviewStoriesDescription',
|
||||
saved_tags: 'PremiumPreviewTagsDescription2',
|
||||
last_seen: 'PremiumPreviewLastSeenDescription',
|
||||
message_privacy: 'PremiumPreviewMessagePrivacyDescription',
|
||||
};
|
||||
|
||||
export const PREMIUM_FEATURE_SECTIONS = [
|
||||
'stories',
|
||||
'double_limits',
|
||||
'more_upload',
|
||||
'faster_download',
|
||||
'voice_to_text',
|
||||
'no_ads',
|
||||
'infinite_reactions',
|
||||
'premium_stickers',
|
||||
'animated_emoji',
|
||||
'advanced_chat_management',
|
||||
'profile_badge',
|
||||
'animated_userpics',
|
||||
'emoji_status',
|
||||
'translations',
|
||||
'saved_tags',
|
||||
];
|
||||
|
||||
const PREMIUM_BOTTOM_VIDEOS: string[] = [
|
||||
'faster_download',
|
||||
'voice_to_text',
|
||||
'advanced_chat_management',
|
||||
'infinite_reactions',
|
||||
'profile_badge',
|
||||
'animated_userpics',
|
||||
'emoji_status',
|
||||
'translations',
|
||||
'saved_tags',
|
||||
];
|
||||
|
||||
type ApiLimitTypeWithoutUpload = Exclude<ApiLimitType,
|
||||
'uploadMaxFileparts' | 'chatlistInvites' | 'chatlistJoined' | 'savedDialogsPinned'
|
||||
>;
|
||||
|
||||
const LIMITS_ORDER: ApiLimitTypeWithoutUpload[] = [
|
||||
'channels',
|
||||
'dialogFolderPinned',
|
||||
'channelsPublic',
|
||||
'savedGifs',
|
||||
'stickersFaved',
|
||||
'aboutLength',
|
||||
'captionLength',
|
||||
'dialogFilters',
|
||||
'dialogFiltersChats',
|
||||
'recommendedChannels',
|
||||
];
|
||||
|
||||
const LIMITS_TITLES: Record<ApiLimitTypeWithoutUpload, string> = {
|
||||
const LIMITS_TITLES: Record<ApiLimitTypeForPromo, string> = {
|
||||
channels: 'GroupsAndChannelsLimitTitle',
|
||||
dialogFolderPinned: 'PinChatsLimitTitle',
|
||||
channelsPublic: 'PublicLinksLimitTitle',
|
||||
@ -120,7 +81,7 @@ const LIMITS_TITLES: Record<ApiLimitTypeWithoutUpload, string> = {
|
||||
recommendedChannels: 'SimilarChannelsLimitTitle',
|
||||
};
|
||||
|
||||
const LIMITS_DESCRIPTIONS: Record<ApiLimitTypeWithoutUpload, string> = {
|
||||
const LIMITS_DESCRIPTIONS: Record<ApiLimitTypeForPromo, string> = {
|
||||
channels: 'GroupsAndChannelsLimitSubtitle',
|
||||
dialogFolderPinned: 'PinChatsLimitSubtitle',
|
||||
channelsPublic: 'PublicLinksLimitSubtitle',
|
||||
@ -136,11 +97,12 @@ const LIMITS_DESCRIPTIONS: Record<ApiLimitTypeWithoutUpload, string> = {
|
||||
const BORDER_THRESHOLD = 20;
|
||||
|
||||
type OwnProps = {
|
||||
initialSection: string;
|
||||
initialSection: ApiPremiumSection;
|
||||
promo: ApiPremiumPromo;
|
||||
isPremium?: boolean;
|
||||
limits?: NonNullable<GlobalState['appConfig']>['limits'];
|
||||
premiumPromoOrder?: string[];
|
||||
premiumPromoOrder?: ApiPremiumSection[];
|
||||
subscriptionOption?: ApiPremiumSubscriptionOption;
|
||||
onBack: VoidFunction;
|
||||
onClickSubscribe: (startParam?: string) => void;
|
||||
};
|
||||
@ -151,6 +113,7 @@ const PremiumFeatureModal: FC<OwnProps> = ({
|
||||
isPremium,
|
||||
limits,
|
||||
premiumPromoOrder,
|
||||
subscriptionOption,
|
||||
onBack,
|
||||
onClickSubscribe,
|
||||
}) => {
|
||||
@ -171,27 +134,42 @@ const PremiumFeatureModal: FC<OwnProps> = ({
|
||||
return premiumPromoOrder.filter((section) => PREMIUM_FEATURE_SECTIONS.includes(section));
|
||||
}, [premiumPromoOrder]);
|
||||
|
||||
function handleClick() {
|
||||
const subscriptionButtonText = useMemo(() => {
|
||||
if (!subscriptionOption) return undefined;
|
||||
|
||||
const { amount, months, currency } = subscriptionOption;
|
||||
const perMonthPrice = Math.floor(amount / months);
|
||||
|
||||
return isPremium ? lang('OK') : lang('SubscribeToPremium', formatCurrency(perMonthPrice, currency, lang.code));
|
||||
}, [isPremium, lang, subscriptionOption]);
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
onClickSubscribe(initialSection);
|
||||
}
|
||||
});
|
||||
|
||||
function handleScroll(e: React.UIEvent<HTMLDivElement>) {
|
||||
const { clientWidth, scrollLeft: scrollLeftOriginal } = e.currentTarget;
|
||||
const target = e.currentTarget;
|
||||
const { clientWidth, scrollLeft: scrollLeftOriginal } = target;
|
||||
|
||||
const scrollLeft = Math.round(scrollLeftOriginal);
|
||||
|
||||
const left = scrollLeft % (clientWidth);
|
||||
const progress = left / (clientWidth);
|
||||
e.currentTarget.style.setProperty('--scroll-progress', progress.toString());
|
||||
e.currentTarget.style.setProperty('--abs-scroll-progress', Math.abs(progress).toString());
|
||||
|
||||
const reverseIndex = Math.ceil((scrollLeft + 1) / clientWidth);
|
||||
|
||||
setReverseAnimationSlideIndex(reverseIndex);
|
||||
|
||||
const prevElement = e.currentTarget.querySelector(`#premium_feature_preview_video_${reverseIndex - 1}`);
|
||||
const reverseElement = e.currentTarget.querySelector(`#premium_feature_preview_video_${reverseIndex}`);
|
||||
prevElement?.classList.toggle('reverse', false);
|
||||
reverseElement?.classList.toggle('reverse', true);
|
||||
const prevElement = target.querySelector<HTMLDivElement>(`#premium_feature_preview_video_${reverseIndex - 1}`);
|
||||
const reverseElement = target.querySelector<HTMLDivElement>(`#premium_feature_preview_video_${reverseIndex}`);
|
||||
|
||||
requestMutation(() => {
|
||||
target.style.setProperty('--scroll-progress', progress.toString());
|
||||
target.style.setProperty('--abs-scroll-progress', Math.abs(progress).toString());
|
||||
|
||||
if (prevElement) toggleExtraClass(prevElement, 'reverse', false);
|
||||
if (reverseElement) toggleExtraClass(reverseElement, 'reverse', true);
|
||||
});
|
||||
|
||||
if (isScrolling) return;
|
||||
const slide = Math.round(scrollLeft / clientWidth);
|
||||
@ -215,7 +193,7 @@ const PremiumFeatureModal: FC<OwnProps> = ({
|
||||
.then(stopScrolling);
|
||||
}, [currentSlideIndex, filteredSections, initialSection, prevInitialSection]);
|
||||
|
||||
const handleSelectSlide = useCallback(async (index: number) => {
|
||||
const handleSelectSlide = useLastCallback(async (index: number) => {
|
||||
const scrollContainer = scrollContainerRef.current;
|
||||
if (!scrollContainer) return;
|
||||
|
||||
@ -224,10 +202,7 @@ const PremiumFeatureModal: FC<OwnProps> = ({
|
||||
startScrolling();
|
||||
await animateHorizontalScroll(scrollContainer, scrollContainer.clientWidth * index, 800);
|
||||
stopScrolling();
|
||||
}, []);
|
||||
|
||||
// TODO Support all subscription options
|
||||
const month = promo.options.find((option) => option.months === 1)!;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
@ -254,7 +229,7 @@ const PremiumFeatureModal: FC<OwnProps> = ({
|
||||
{lang(PREMIUM_FEATURE_TITLES.double_limits)}
|
||||
</h2>
|
||||
<div className={buildClassName(styles.limitsContent, 'custom-scroll')} onScroll={handleLimitsScroll}>
|
||||
{LIMITS_ORDER.map((limit, i) => {
|
||||
{PREMIUM_LIMITS_ORDER.map((limit, i) => {
|
||||
const defaultLimit = limits?.[limit][0].toString();
|
||||
const premiumLimit = limits?.[limit][1].toString();
|
||||
return (
|
||||
@ -263,7 +238,7 @@ const PremiumFeatureModal: FC<OwnProps> = ({
|
||||
description={lang(LIMITS_DESCRIPTIONS[limit], premiumLimit)}
|
||||
leftValue={defaultLimit}
|
||||
rightValue={premiumLimit}
|
||||
colorStepProgress={i / (LIMITS_ORDER.length - 1)}
|
||||
colorStepProgress={i / (PREMIUM_LIMITS_ORDER.length - 1)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@ -333,16 +308,16 @@ const PremiumFeatureModal: FC<OwnProps> = ({
|
||||
active={currentSlideIndex}
|
||||
onSelectSlide={handleSelectSlide}
|
||||
/>
|
||||
<Button
|
||||
className={buildClassName(styles.button)}
|
||||
isShiny={!isPremium}
|
||||
withPremiumGradient={!isPremium}
|
||||
onClick={isPremium ? onBack : handleClick}
|
||||
>
|
||||
{isPremium
|
||||
? lang('OK')
|
||||
: lang('SubscribeToPremium', formatCurrency(Number(month.amount), month.currency, lang.code))}
|
||||
</Button>
|
||||
{subscriptionButtonText && (
|
||||
<Button
|
||||
className={buildClassName(styles.button)}
|
||||
isShiny={!isPremium}
|
||||
withPremiumGradient={!isPremium}
|
||||
onClick={isPremium ? onBack : handleClick}
|
||||
>
|
||||
{subscriptionButtonText}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useCallback, useEffect, useMemo, useRef, useState,
|
||||
memo, useEffect, useMemo, useRef, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type {
|
||||
ApiPremiumPromo, ApiPremiumSubscriptionOption, ApiSticker, ApiStickerSet, ApiUser,
|
||||
} from '../../../api/types';
|
||||
import type { GlobalState } from '../../../global/types';
|
||||
import type { ApiPremiumSection, GlobalState } from '../../../global/types';
|
||||
|
||||
import { TME_LINK_PREFIX } from '../../../config';
|
||||
import { PREMIUM_FEATURE_SECTIONS, TME_LINK_PREFIX } from '../../../config';
|
||||
import { getUserFullName } from '../../../global/helpers';
|
||||
import {
|
||||
selectIsCurrentUserPremium, selectStickerSet,
|
||||
@ -33,7 +33,6 @@ import Transition from '../../ui/Transition';
|
||||
import PremiumFeatureItem from './PremiumFeatureItem';
|
||||
import PremiumFeatureModal, {
|
||||
PREMIUM_FEATURE_DESCRIPTIONS,
|
||||
PREMIUM_FEATURE_SECTIONS,
|
||||
PREMIUM_FEATURE_TITLES,
|
||||
} from './PremiumFeatureModal';
|
||||
import PremiumSubscriptionOption from './PremiumSubscriptionOption';
|
||||
@ -45,8 +44,10 @@ import PremiumBadge from '../../../assets/premium/PremiumBadge.svg';
|
||||
import PremiumChats from '../../../assets/premium/PremiumChats.svg';
|
||||
import PremiumEmoji from '../../../assets/premium/PremiumEmoji.svg';
|
||||
import PremiumFile from '../../../assets/premium/PremiumFile.svg';
|
||||
import PremiumLastSeen from '../../../assets/premium/PremiumLastSeen.svg';
|
||||
import PremiumLimits from '../../../assets/premium/PremiumLimits.svg';
|
||||
import PremiumLogo from '../../../assets/premium/PremiumLogo.svg';
|
||||
import PremiumMessagePrivacy from '../../../assets/premium/PremiumMessagePrivacy.svg';
|
||||
import PremiumReactions from '../../../assets/premium/PremiumReactions.svg';
|
||||
import PremiumSpeed from '../../../assets/premium/PremiumSpeed.svg';
|
||||
import PremiumStatus from '../../../assets/premium/PremiumStatus.svg';
|
||||
@ -59,7 +60,7 @@ import PremiumVoice from '../../../assets/premium/PremiumVoice.svg';
|
||||
const LIMIT_ACCOUNTS = 4;
|
||||
const STATUS_EMOJI_SIZE = 8 * REM;
|
||||
|
||||
const PREMIUM_FEATURE_COLOR_ICONS: Record<string, string> = {
|
||||
const PREMIUM_FEATURE_COLOR_ICONS: Record<ApiPremiumSection, string> = {
|
||||
stories: PremiumStatus,
|
||||
double_limits: PremiumLimits,
|
||||
infinite_reactions: PremiumReactions,
|
||||
@ -75,6 +76,8 @@ const PREMIUM_FEATURE_COLOR_ICONS: Record<string, string> = {
|
||||
emoji_status: PremiumStatus,
|
||||
translations: PremiumTranslate,
|
||||
saved_tags: PremiumTags,
|
||||
last_seen: PremiumLastSeen,
|
||||
message_privacy: PremiumMessagePrivacy,
|
||||
};
|
||||
|
||||
export type OwnProps = {
|
||||
@ -84,12 +87,11 @@ export type OwnProps = {
|
||||
type StateProps = {
|
||||
currentUserId?: string;
|
||||
promo?: ApiPremiumPromo;
|
||||
isClosing?: boolean;
|
||||
fromUser?: ApiUser;
|
||||
fromUserStatusEmoji?: ApiSticker;
|
||||
fromUserStatusSet?: ApiStickerSet;
|
||||
toUser?: ApiUser;
|
||||
initialSection?: string;
|
||||
initialSection?: ApiPremiumSection;
|
||||
isPremium?: boolean;
|
||||
isSuccess?: boolean;
|
||||
isGift?: boolean;
|
||||
@ -101,7 +103,7 @@ type StateProps = {
|
||||
limits?: NonNullable<GlobalState['appConfig']>['limits'];
|
||||
premiumSlug?: string;
|
||||
premiumBotUsername?: string;
|
||||
premiumPromoOrder?: string[];
|
||||
premiumPromoOrder?: ApiPremiumSection[];
|
||||
};
|
||||
|
||||
const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
||||
@ -120,7 +122,6 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
||||
limits,
|
||||
premiumSlug,
|
||||
premiumBotUsername,
|
||||
isClosing,
|
||||
isSuccess,
|
||||
isGift,
|
||||
toUser,
|
||||
@ -135,14 +136,23 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
const [isHeaderHidden, setHeaderHidden] = useState(true);
|
||||
const [currentSection, setCurrentSection] = useState<string | undefined>(initialSection);
|
||||
const [currentSection, setCurrentSection] = useState<ApiPremiumSection | undefined>(initialSection);
|
||||
const [selectedSubscriptionOption, setSubscriptionOption] = useState<ApiPremiumSubscriptionOption>();
|
||||
|
||||
const handleOpen = useCallback((section: string | undefined) => {
|
||||
return () => {
|
||||
setCurrentSection(section);
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setHeaderHidden(true);
|
||||
setCurrentSection(undefined);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const handleOpenSection = useLastCallback((section: ApiPremiumSection) => {
|
||||
setCurrentSection(section);
|
||||
});
|
||||
|
||||
const handleResetSection = useLastCallback(() => {
|
||||
setCurrentSection(undefined);
|
||||
});
|
||||
|
||||
function handleScroll(e: React.UIEvent<HTMLDivElement>) {
|
||||
const { scrollTop } = e.currentTarget;
|
||||
@ -166,20 +176,20 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
});
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
const handleClick = useLastCallback(() => {
|
||||
if (selectedSubscriptionOption) {
|
||||
handleClickWithStartParam(String(selectedSubscriptionOption.months));
|
||||
} else {
|
||||
handleClickWithStartParam();
|
||||
}
|
||||
}, [selectedSubscriptionOption, handleClickWithStartParam]);
|
||||
});
|
||||
|
||||
const handleChangeSubscriptionOption = useCallback((months: number) => {
|
||||
const handleChangeSubscriptionOption = useLastCallback((months: number) => {
|
||||
const foundOption = promo?.options.find((option) => option.months === months);
|
||||
setSubscriptionOption(foundOption);
|
||||
}, [promo]);
|
||||
});
|
||||
|
||||
const showConfetti = useCallback(() => {
|
||||
const showConfetti = useLastCallback(() => {
|
||||
const dialog = dialogRef.current;
|
||||
if (!dialog) return;
|
||||
if (isOpen) {
|
||||
@ -193,7 +203,7 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
||||
height,
|
||||
});
|
||||
}
|
||||
}, [isOpen, requestConfetti]);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
@ -336,10 +346,8 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
||||
return (
|
||||
<Modal
|
||||
className={styles.root}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onCloseAnimationEnd={() => closePremiumModal({ isClosed: true })}
|
||||
onClose={closePremiumModal}
|
||||
isOpen={isOpen && !isClosing}
|
||||
isOpen={isOpen}
|
||||
dialogRef={dialogRef}
|
||||
>
|
||||
<Transition name="slide" activeKey={currentSection ? 1 : 0} className={styles.transition}>
|
||||
@ -392,7 +400,8 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
||||
icon={PREMIUM_FEATURE_COLOR_ICONS[section]}
|
||||
index={index}
|
||||
count={filteredSections.length}
|
||||
onClick={handleOpen(section)}
|
||||
section={section}
|
||||
onClick={handleOpenSection}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@ -420,13 +429,13 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
||||
) : (
|
||||
<PremiumFeatureModal
|
||||
initialSection={currentSection}
|
||||
onBack={handleOpen(undefined)}
|
||||
onBack={handleResetSection}
|
||||
promo={promo}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClickSubscribe={handleClickWithStartParam}
|
||||
isPremium={isPremium}
|
||||
limits={limits}
|
||||
premiumPromoOrder={premiumPromoOrder}
|
||||
subscriptionOption={selectedSubscriptionOption}
|
||||
/>
|
||||
)}
|
||||
</Transition>
|
||||
@ -448,7 +457,6 @@ export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
return {
|
||||
currentUserId: global.currentUserId,
|
||||
promo: premiumModal?.promo,
|
||||
isClosing: premiumModal?.isClosing,
|
||||
isSuccess: premiumModal?.isSuccess,
|
||||
isGift: premiumModal?.isGift,
|
||||
monthsAmount: premiumModal?.monthsAmount,
|
||||
|
||||
@ -108,6 +108,7 @@ const PremiumFeaturePreviewVideo = ({
|
||||
isFontIcon
|
||||
index={index}
|
||||
count={STORY_FEATURE_ORDER.length}
|
||||
section={section}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -217,9 +217,9 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
setViewForumAsMessages({ chatId, isEnabled: true });
|
||||
});
|
||||
|
||||
function handleRequestCall() {
|
||||
const handleRequestCall = useLastCallback(() => {
|
||||
requestMasterAndRequestCall({ userId: chatId });
|
||||
}
|
||||
});
|
||||
|
||||
const handleHotkeySearchClick = useLastCallback((e: KeyboardEvent) => {
|
||||
if (!canSearch || !IS_APP || e.shiftKey) {
|
||||
@ -384,7 +384,6 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
round
|
||||
color="translucent"
|
||||
size="smaller"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={handleRequestCall}
|
||||
ariaLabel="Call"
|
||||
>
|
||||
|
||||
@ -19,6 +19,7 @@ import { formatIntegerCompact } from '../../../util/textFormat';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useMedia from '../../../hooks/useMedia';
|
||||
import useTimeout from '../../../hooks/useTimeout';
|
||||
|
||||
@ -92,7 +93,7 @@ const SimilarChannels = ({
|
||||
return undefined;
|
||||
}, [similarChannels, shouldShowInChat, shoulRenderSkeleton]);
|
||||
|
||||
const handleToggle = () => {
|
||||
const handleToggle = useLastCallback(() => {
|
||||
toggleChannelRecommendations({ chatId });
|
||||
if (shouldShowInChat) {
|
||||
markNotShowing();
|
||||
@ -101,7 +102,7 @@ const SimilarChannels = ({
|
||||
markShowing();
|
||||
markNotHiding();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={buildClassName(styles.root)}>
|
||||
@ -144,7 +145,6 @@ const SimilarChannels = ({
|
||||
<Button
|
||||
className={styles.close}
|
||||
color="translucent"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={handleToggle}
|
||||
>
|
||||
<Icon name="close" />
|
||||
|
||||
@ -159,8 +159,7 @@ const StoryView = ({
|
||||
styles.opacityFadeIn,
|
||||
(storyView.isUserBlocked || storyView.areStoriesBlocked) && styles.blocked,
|
||||
)}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => handleClick()}
|
||||
onClick={handleClick}
|
||||
rightElement={storyView.type === 'user' && storyView.reaction ? (
|
||||
<ReactionStaticEmoji
|
||||
reaction={storyView.reaction}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { ApiReactionEmoji } from './api/types';
|
||||
import type { ApiLimitType } from './global/types';
|
||||
import type { ApiLimitType, ApiLimitTypeForPromo, ApiPremiumSection } from './global/types';
|
||||
|
||||
export const APP_CODE_NAME = 'A';
|
||||
export const APP_NAME = process.env.APP_NAME || `Telegram Web ${APP_CODE_NAME}`;
|
||||
@ -347,3 +347,51 @@ export const DEFAULT_LIMITS: Record<ApiLimitType, readonly [number, number]> = {
|
||||
};
|
||||
|
||||
export const ONE_TIME_MEDIA_TTL_SECONDS = 2147483647;
|
||||
|
||||
// Premium
|
||||
export const PREMIUM_FEATURE_SECTIONS = [
|
||||
'stories',
|
||||
'double_limits',
|
||||
'more_upload',
|
||||
'faster_download',
|
||||
'voice_to_text',
|
||||
'no_ads',
|
||||
'infinite_reactions',
|
||||
'premium_stickers',
|
||||
'animated_emoji',
|
||||
'advanced_chat_management',
|
||||
'profile_badge',
|
||||
'animated_userpics',
|
||||
'emoji_status',
|
||||
'translations',
|
||||
'saved_tags',
|
||||
'last_seen',
|
||||
'message_privacy',
|
||||
] as const;
|
||||
|
||||
export const PREMIUM_BOTTOM_VIDEOS: ApiPremiumSection[] = [
|
||||
'faster_download',
|
||||
'voice_to_text',
|
||||
'advanced_chat_management',
|
||||
'infinite_reactions',
|
||||
'profile_badge',
|
||||
'animated_userpics',
|
||||
'emoji_status',
|
||||
'translations',
|
||||
'saved_tags',
|
||||
'last_seen',
|
||||
'message_privacy',
|
||||
];
|
||||
|
||||
export const PREMIUM_LIMITS_ORDER: ApiLimitTypeForPromo[] = [
|
||||
'channels',
|
||||
'dialogFolderPinned',
|
||||
'channelsPublic',
|
||||
'savedGifs',
|
||||
'stickersFaved',
|
||||
'aboutLength',
|
||||
'captionLength',
|
||||
'dialogFilters',
|
||||
'dialogFiltersChats',
|
||||
'recommendedChannels',
|
||||
];
|
||||
|
||||
@ -363,15 +363,14 @@ addActionHandler('setPaymentStep', (global, actions, payload): ActionReturnType
|
||||
});
|
||||
|
||||
addActionHandler('closePremiumModal', (global, actions, payload): ActionReturnType => {
|
||||
const { isClosed, tabId = getCurrentTabId() } = payload || {};
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
const tabState = selectTabState(global, tabId);
|
||||
if (!tabState.premiumModal) return undefined;
|
||||
return updateTabState(global, {
|
||||
premiumModal: {
|
||||
...tabState.premiumModal,
|
||||
...(isClosed && { isOpen: false }),
|
||||
isClosing: !isClosed,
|
||||
promo: tabState.premiumModal.promo, // Cache promo
|
||||
isOpen: false,
|
||||
},
|
||||
}, tabId);
|
||||
});
|
||||
@ -415,15 +414,10 @@ addActionHandler('openGiftPremiumModal', async (global, actions, payload): Promi
|
||||
global = getGlobal();
|
||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||
|
||||
// TODO Support all subscription options
|
||||
const month = result.promo.options.find((option) => option.months === 1)!;
|
||||
|
||||
global = updateTabState(global, {
|
||||
giftPremiumModal: {
|
||||
isOpen: true,
|
||||
forUserId,
|
||||
monthlyCurrency: month.currency,
|
||||
monthlyAmount: String(month.amount),
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
@ -75,6 +75,7 @@ import type {
|
||||
ApiWebSession,
|
||||
} from '../api/types';
|
||||
import type { ApiCredentials } from '../components/payment/PaymentModal';
|
||||
import type { PREMIUM_FEATURE_SECTIONS } from '../config';
|
||||
import type { FoldersActions } from '../hooks/reducers/useFoldersReducer';
|
||||
import type { ReducerAction } from '../hooks/useReducer';
|
||||
import type { P2pMessage } from '../lib/secret-sauce';
|
||||
@ -194,6 +195,12 @@ export type ApiLimitTypeWithModal = Exclude<ApiLimitType, (
|
||||
'captionLength' | 'aboutLength' | 'stickersFaved' | 'savedGifs' | 'recommendedChannels'
|
||||
)>;
|
||||
|
||||
export type ApiLimitTypeForPromo = Exclude<ApiLimitType,
|
||||
'uploadMaxFileparts' | 'chatlistInvites' | 'chatlistJoined' | 'savedDialogsPinned'
|
||||
>;
|
||||
|
||||
export type ApiPremiumSection = typeof PREMIUM_FEATURE_SECTIONS[number];
|
||||
|
||||
export type TranslatedMessage = {
|
||||
isPending?: boolean;
|
||||
text?: ApiFormattedText;
|
||||
@ -607,9 +614,8 @@ export type TabState = {
|
||||
|
||||
premiumModal?: {
|
||||
isOpen?: boolean;
|
||||
isClosing?: boolean;
|
||||
promo: ApiPremiumPromo;
|
||||
initialSection?: string;
|
||||
initialSection?: ApiPremiumSection;
|
||||
fromUserId?: string;
|
||||
toUserId?: string;
|
||||
isGift?: boolean;
|
||||
@ -620,8 +626,6 @@ export type TabState = {
|
||||
giftPremiumModal?: {
|
||||
isOpen?: boolean;
|
||||
forUserId?: string;
|
||||
monthlyCurrency?: string;
|
||||
monthlyAmount?: string;
|
||||
};
|
||||
|
||||
limitReachedModal?: {
|
||||
@ -2877,16 +2881,14 @@ export interface ActionPayloads {
|
||||
|
||||
// Premium
|
||||
openPremiumModal: ({
|
||||
initialSection?: string;
|
||||
initialSection?: ApiPremiumSection;
|
||||
fromUserId?: string;
|
||||
toUserId?: string;
|
||||
isSuccess?: boolean;
|
||||
isGift?: boolean;
|
||||
monthsAmount?: number;
|
||||
} & WithTabId) | undefined;
|
||||
closePremiumModal: ({
|
||||
isClosed?: boolean;
|
||||
} & WithTabId) | undefined;
|
||||
closePremiumModal: WithTabId | undefined;
|
||||
|
||||
transcribeAudio: {
|
||||
chatId: string;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user