diff --git a/src/api/gramjs/apiBuilders/appConfig.ts b/src/api/gramjs/apiBuilders/appConfig.ts index 6ed51aa58..6542fbaf6 100644 --- a/src/api/gramjs/apiBuilders/appConfig.ts +++ b/src/api/gramjs/apiBuilders/appConfig.ts @@ -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, diff --git a/src/api/gramjs/apiBuilders/payments.ts b/src/api/gramjs/apiBuilders/payments.ts index 483486657..5044d9f7b 100644 --- a/src/api/gramjs/apiBuilders/payments.ts +++ b/src/api/gramjs/apiBuilders/payments.ts @@ -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), }; diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index 31cbd5863..ac700f211 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -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; diff --git a/src/api/types/payments.ts b/src/api/types/payments.ts index b17f22d66..3ab9d5f39 100644 --- a/src/api/types/payments.ts +++ b/src/api/types/payments.ts @@ -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[]; diff --git a/src/assets/premium/PremiumLastSeen.svg b/src/assets/premium/PremiumLastSeen.svg new file mode 100644 index 000000000..6331bf8f4 --- /dev/null +++ b/src/assets/premium/PremiumLastSeen.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/premium/PremiumMessagePrivacy.svg b/src/assets/premium/PremiumMessagePrivacy.svg new file mode 100644 index 000000000..e94152e0c --- /dev/null +++ b/src/assets/premium/PremiumMessagePrivacy.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/common/ReadDateModal.tsx b/src/components/common/ReadDateModal.tsx index 429cfcb09..ce24d683f 100644 --- a/src/components/common/ReadDateModal.tsx +++ b/src/components/common/ReadDateModal.tsx @@ -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 ( - +
diff --git a/src/components/common/SliderDots.tsx b/src/components/common/SliderDots.tsx index 94d705589..56a35f061 100644 --- a/src/components/common/SliderDots.tsx +++ b/src/components/common/SliderDots.tsx @@ -66,7 +66,7 @@ const SliderDots: FC = ({ styles.dot, index === active && styles.active, (isPreLast || isPreFirst) && styles.medium, - (isLast || isFirst) && styles.small, + (isLast || isFirst || isInvisible) && styles.small, isInvisible && styles.invisible, )} /> diff --git a/src/components/left/settings/SettingsExperimental.tsx b/src/components/left/settings/SettingsExperimental.tsx index 3a46ffcc6..d32d53a05 100644 --- a/src/components/left/settings/SettingsExperimental.tsx +++ b/src/components/left/settings/SettingsExperimental.tsx @@ -129,7 +129,6 @@ const SettingsExperimental: FC = ({ )} diff --git a/src/components/left/settings/SettingsPrivacyLastSeen.tsx b/src/components/left/settings/SettingsPrivacyLastSeen.tsx index ff7c90e0f..138a08370 100644 --- a/src/components/left/settings/SettingsPrivacyLastSeen.tsx +++ b/src/components/left/settings/SettingsPrivacyLastSeen.tsx @@ -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 = ({
} - // eslint-disable-next-line react/jsx-no-bind - onClick={() => openPremiumModal()} + onClick={handleOpenPremiumModal} > {isCurrentUserPremium ? lang('PrivacyLastSeenPremiumForPremium') : lang('PrivacyLastSeenPremium')} diff --git a/src/components/left/settings/folders/SettingsFoldersEdit.tsx b/src/components/left/settings/folders/SettingsFoldersEdit.tsx index ff0a7ef43..9bf7e39d5 100644 --- a/src/components/left/settings/folders/SettingsFoldersEdit.tsx +++ b/src/components/left/settings/folders/SettingsFoldersEdit.tsx @@ -358,7 +358,6 @@ const SettingsFoldersEdit: FC = ({ className="settings-folders-list-item mb-0" icon="link" multiline - // eslint-disable-next-line react/jsx-no-bind onClick={handleEditInviteClick} clickArg={invite.url} > diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index cfc8a4f36..b28b07c26 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -591,7 +591,7 @@ const Main: FC = ({ - {isPremiumModalOpen && } + diff --git a/src/components/main/premium/GiftPremiumModal.tsx b/src/components/main/premium/GiftPremiumModal.tsx index f0042faba..9c5c2f8aa 100644 --- a/src/components/main/premium/GiftPremiumModal.tsx +++ b/src/components/main/premium/GiftPremiumModal.tsx @@ -41,8 +41,6 @@ const GiftPremiumModal: FC = ({ isOpen, user, gifts, - monthlyCurrency, - monthlyAmount, }) => { const { openPremiumModal, closeGiftPremiumModal, openUrl } = getActions(); @@ -56,14 +54,12 @@ const GiftPremiumModal: FC = ({ 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 = ({ }; export default memo(withGlobal((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)); diff --git a/src/components/main/premium/PremiumFeatureItem.tsx b/src/components/main/premium/PremiumFeatureItem.tsx index 2fd94a177..6cc4cea7f 100644 --- a/src/components/main/premium/PremiumFeatureItem.tsx +++ b/src/components/main/premium/PremiumFeatureItem.tsx @@ -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 = { 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 = ({ +// eslint-disable-next-line @typescript-eslint/comma-dangle +const PremiumFeatureItem = ({ icon, isFontIcon, title, text, index, count, + section, onClick, -}) => { +}: OwnProps) => { 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 ( - + {isFontIcon ? ( = { +export const PREMIUM_FEATURE_TITLES: Record = { double_limits: 'PremiumPreviewLimits', infinite_reactions: 'PremiumPreviewReactions2', premium_stickers: 'PremiumPreviewStickers', @@ -40,9 +44,11 @@ export const PREMIUM_FEATURE_TITLES: Record = { translations: 'PremiumPreviewTranslations', stories: 'PremiumPreviewStories', saved_tags: 'PremiumPreviewTags2', + last_seen: 'PremiumPreviewLastSeen', + message_privacy: 'PremiumPreviewMessagePrivacy', }; -export const PREMIUM_FEATURE_DESCRIPTIONS: Record = { +export const PREMIUM_FEATURE_DESCRIPTIONS: Record = { double_limits: 'PremiumPreviewLimitsDescription', infinite_reactions: 'PremiumPreviewReactions2Description', premium_stickers: 'PremiumPreviewStickersDescription', @@ -58,56 +64,11 @@ export const PREMIUM_FEATURE_DESCRIPTIONS: Record = { 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; - -const LIMITS_ORDER: ApiLimitTypeWithoutUpload[] = [ - 'channels', - 'dialogFolderPinned', - 'channelsPublic', - 'savedGifs', - 'stickersFaved', - 'aboutLength', - 'captionLength', - 'dialogFilters', - 'dialogFiltersChats', - 'recommendedChannels', -]; - -const LIMITS_TITLES: Record = { +const LIMITS_TITLES: Record = { channels: 'GroupsAndChannelsLimitTitle', dialogFolderPinned: 'PinChatsLimitTitle', channelsPublic: 'PublicLinksLimitTitle', @@ -120,7 +81,7 @@ const LIMITS_TITLES: Record = { recommendedChannels: 'SimilarChannelsLimitTitle', }; -const LIMITS_DESCRIPTIONS: Record = { +const LIMITS_DESCRIPTIONS: Record = { channels: 'GroupsAndChannelsLimitSubtitle', dialogFolderPinned: 'PinChatsLimitSubtitle', channelsPublic: 'PublicLinksLimitSubtitle', @@ -136,11 +97,12 @@ const LIMITS_DESCRIPTIONS: Record = { const BORDER_THRESHOLD = 20; type OwnProps = { - initialSection: string; + initialSection: ApiPremiumSection; promo: ApiPremiumPromo; isPremium?: boolean; limits?: NonNullable['limits']; - premiumPromoOrder?: string[]; + premiumPromoOrder?: ApiPremiumSection[]; + subscriptionOption?: ApiPremiumSubscriptionOption; onBack: VoidFunction; onClickSubscribe: (startParam?: string) => void; }; @@ -151,6 +113,7 @@ const PremiumFeatureModal: FC = ({ isPremium, limits, premiumPromoOrder, + subscriptionOption, onBack, onClickSubscribe, }) => { @@ -171,27 +134,42 @@ const PremiumFeatureModal: FC = ({ 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) { - 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(`#premium_feature_preview_video_${reverseIndex - 1}`); + const reverseElement = target.querySelector(`#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 = ({ .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 = ({ startScrolling(); await animateHorizontalScroll(scrollContainer, scrollContainer.clientWidth * index, 800); stopScrolling(); - }, []); - - // TODO Support all subscription options - const month = promo.options.find((option) => option.months === 1)!; + }); return (
@@ -254,7 +229,7 @@ const PremiumFeatureModal: FC = ({ {lang(PREMIUM_FEATURE_TITLES.double_limits)}
- {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 = ({ 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 = ({ active={currentSlideIndex} onSelectSlide={handleSelectSlide} /> - + {subscriptionButtonText && ( + + )}
); diff --git a/src/components/main/premium/PremiumMainModal.tsx b/src/components/main/premium/PremiumMainModal.tsx index 9fb9c3ea6..fab84f77a 100644 --- a/src/components/main/premium/PremiumMainModal.tsx +++ b/src/components/main/premium/PremiumMainModal.tsx @@ -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 = { +const PREMIUM_FEATURE_COLOR_ICONS: Record = { stories: PremiumStatus, double_limits: PremiumLimits, infinite_reactions: PremiumReactions, @@ -75,6 +76,8 @@ const PREMIUM_FEATURE_COLOR_ICONS: Record = { 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['limits']; premiumSlug?: string; premiumBotUsername?: string; - premiumPromoOrder?: string[]; + premiumPromoOrder?: ApiPremiumSection[]; }; const PremiumMainModal: FC = ({ @@ -120,7 +122,6 @@ const PremiumMainModal: FC = ({ limits, premiumSlug, premiumBotUsername, - isClosing, isSuccess, isGift, toUser, @@ -135,14 +136,23 @@ const PremiumMainModal: FC = ({ const lang = useLang(); const [isHeaderHidden, setHeaderHidden] = useState(true); - const [currentSection, setCurrentSection] = useState(initialSection); + const [currentSection, setCurrentSection] = useState(initialSection); const [selectedSubscriptionOption, setSubscriptionOption] = useState(); - 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) { const { scrollTop } = e.currentTarget; @@ -166,20 +176,20 @@ const PremiumMainModal: FC = ({ } }); - 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 = ({ height, }); } - }, [isOpen, requestConfetti]); + }); useEffect(() => { if (isSuccess) { @@ -336,10 +346,8 @@ const PremiumMainModal: FC = ({ return ( closePremiumModal({ isClosed: true })} onClose={closePremiumModal} - isOpen={isOpen && !isClosing} + isOpen={isOpen} dialogRef={dialogRef} > @@ -392,7 +400,8 @@ const PremiumMainModal: FC = ({ 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 = ({ ) : ( )} @@ -448,7 +457,6 @@ export default memo(withGlobal((global): StateProps => { return { currentUserId: global.currentUserId, promo: premiumModal?.promo, - isClosing: premiumModal?.isClosing, isSuccess: premiumModal?.isSuccess, isGift: premiumModal?.isGift, monthsAmount: premiumModal?.monthsAmount, diff --git a/src/components/main/premium/previews/PremiumFeaturePreviewStories.tsx b/src/components/main/premium/previews/PremiumFeaturePreviewStories.tsx index 4049cc486..31b7a1a77 100644 --- a/src/components/main/premium/previews/PremiumFeaturePreviewStories.tsx +++ b/src/components/main/premium/previews/PremiumFeaturePreviewStories.tsx @@ -108,6 +108,7 @@ const PremiumFeaturePreviewVideo = ({ isFontIcon index={index} count={STORY_FEATURE_ORDER.length} + section={section} /> ); })} diff --git a/src/components/middle/HeaderActions.tsx b/src/components/middle/HeaderActions.tsx index 3efff303a..ada229dbb 100644 --- a/src/components/middle/HeaderActions.tsx +++ b/src/components/middle/HeaderActions.tsx @@ -217,9 +217,9 @@ const HeaderActions: FC = ({ 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 = ({ round color="translucent" size="smaller" - // eslint-disable-next-line react/jsx-no-bind onClick={handleRequestCall} ariaLabel="Call" > diff --git a/src/components/middle/message/SimilarChannels.tsx b/src/components/middle/message/SimilarChannels.tsx index df1fabae2..aa80dcffe 100644 --- a/src/components/middle/message/SimilarChannels.tsx +++ b/src/components/middle/message/SimilarChannels.tsx @@ -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 (
@@ -144,7 +145,6 @@ const SimilarChannels = ({