From 05ff8a385bf949ecfa3220413afa3c61d8237f5c Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Wed, 23 Apr 2025 18:59:27 +0200 Subject: [PATCH] Settings: Implement settings privacy for gifts (#5802) --- src/api/gramjs/apiBuilders/gifts.ts | 19 +++ src/api/gramjs/apiBuilders/users.ts | 5 +- src/api/gramjs/gramjsBuilders/index.ts | 10 ++ src/api/gramjs/methods/payments.ts | 18 +- src/api/gramjs/methods/settings.ts | 13 ++ src/api/types/stars.ts | 7 + src/api/types/users.ts | 9 + src/assets/localization/fallback.strings | 21 +++ src/components/common/Composer.tsx | 32 ++++ src/components/left/settings/Settings.scss | 4 + .../left/settings/SettingsAcceptedGift.tsx | 158 ++++++++++++++++++ .../settings/SettingsPrivacyVisibility.tsx | 62 ++++++- src/components/middle/HeaderMenuContainer.tsx | 101 ++++++----- src/components/modals/gift/GiftComposer.tsx | 37 +++- src/components/modals/gift/GiftModal.tsx | 79 +++++++-- .../modals/gift/StarGiftCategoryList.tsx | 4 +- src/global/actions/api/payments.ts | 1 + src/global/actions/api/settings.ts | 10 ++ src/global/initialState.ts | 1 + src/global/types/actions.ts | 3 + src/types/index.ts | 3 + src/types/language.d.ts | 24 +++ src/util/getReadableErrorText.ts | 2 + 23 files changed, 546 insertions(+), 77 deletions(-) create mode 100644 src/components/left/settings/SettingsAcceptedGift.tsx diff --git a/src/api/gramjs/apiBuilders/gifts.ts b/src/api/gramjs/apiBuilders/gifts.ts index bc866bacb..dd33c8083 100644 --- a/src/api/gramjs/apiBuilders/gifts.ts +++ b/src/api/gramjs/apiBuilders/gifts.ts @@ -1,6 +1,7 @@ import { Api as GramJs } from '../../../lib/gramjs'; import type { + ApiDisallowedGiftsSettings, ApiInputSavedStarGift, ApiSavedStarGift, ApiStarGift, @@ -156,3 +157,21 @@ export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId isPinned: pinnedToTop, }; } + +export function buildApiDisallowedGiftsSettings( + result: GramJs.TypeDisallowedGiftsSettings, +): ApiDisallowedGiftsSettings { + const { + disallowUnlimitedStargifts, + disallowLimitedStargifts, + disallowUniqueStargifts, + disallowPremiumGifts, + } = result; + + return { + shouldDisallowUnlimitedStarGifts: disallowUnlimitedStargifts, + shouldDisallowLimitedStarGifts: disallowLimitedStargifts, + shouldDisallowUniqueStarGifts: disallowUniqueStargifts, + shouldDisallowPremiumGifts: disallowPremiumGifts, + }; +} diff --git a/src/api/gramjs/apiBuilders/users.ts b/src/api/gramjs/apiBuilders/users.ts index 19aec1766..274f1cb5d 100644 --- a/src/api/gramjs/apiBuilders/users.ts +++ b/src/api/gramjs/apiBuilders/users.ts @@ -14,6 +14,7 @@ import { buildApiBusinessIntro, buildApiBusinessLocation, buildApiBusinessWorkHo import { buildApiBotVerification, buildApiPhoto, buildApiUsernames, buildAvatarPhotoId, } from './common'; +import { buildApiDisallowedGiftsSettings } from './gifts'; import { omitVirtualClassFields } from './helpers'; import { buildApiEmojiStatus, buildApiPeerColor, buildApiPeerId } from './peers'; @@ -25,7 +26,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse fallbackPhoto, personalPhoto, translationsDisabled, storiesPinnedAvailable, contactRequirePremium, businessWorkHours, businessLocation, businessIntro, birthday, personalChannelId, personalChannelMessage, sponsoredEnabled, stargiftsCount, botVerification, - botCanManageEmojiStatus, settings, sendPaidMessagesStars, + botCanManageEmojiStatus, settings, sendPaidMessagesStars, displayGiftsButton, disallowedGifts, }, users, } = mtpUserFull; @@ -45,6 +46,8 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse personalPhoto: personalPhoto instanceof GramJs.Photo ? buildApiPhoto(personalPhoto) : undefined, botInfo: botInfo && buildApiBotInfo(botInfo, userId), isContactRequirePremium: contactRequirePremium, + shouldDisplayGiftsButton: displayGiftsButton, + disallowedGifts: disallowedGifts && buildApiDisallowedGiftsSettings(disallowedGifts), birthday: birthday && buildApiBirthday(birthday), businessLocation: businessLocation && buildApiBusinessLocation(businessLocation), businessWorkHours: businessWorkHours && buildApiBusinessWorkHours(businessWorkHours), diff --git a/src/api/gramjs/gramjsBuilders/index.ts b/src/api/gramjs/gramjsBuilders/index.ts index 40b47ff06..5f1d45271 100644 --- a/src/api/gramjs/gramjsBuilders/index.ts +++ b/src/api/gramjs/gramjsBuilders/index.ts @@ -8,6 +8,7 @@ import type { ApiChatBannedRights, ApiChatFolder, ApiChatReactions, + ApiDisallowedGiftsSettings, ApiEmojiStatusType, ApiFormattedText, ApiGroupCall, @@ -644,6 +645,15 @@ function buildPremiumGiftCodeOption(optionData: ApiPremiumGiftCodeOption) { }); } +export function buildDisallowedGiftsSettings(disallowedGifts: ApiDisallowedGiftsSettings) { + return new GramJs.DisallowedGiftsSettings({ + disallowUnlimitedStargifts: disallowedGifts.shouldDisallowLimitedStarGifts, + disallowLimitedStargifts: disallowedGifts.shouldDisallowUnlimitedStarGifts, + disallowUniqueStargifts: disallowedGifts.shouldDisallowUniqueStarGifts, + disallowPremiumGifts: disallowedGifts.shouldDisallowPremiumGifts, + }); +} + export function buildInputInvoice(invoice: ApiRequestInputInvoice) { switch (invoice.type) { case 'message': { diff --git a/src/api/gramjs/methods/payments.ts b/src/api/gramjs/methods/payments.ts index 62573b06c..c9a1e59ba 100644 --- a/src/api/gramjs/methods/payments.ts +++ b/src/api/gramjs/methods/payments.ts @@ -1,5 +1,6 @@ import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; +import { RPCError } from '../../../lib/gramjs/errors'; import type { ApiChat, @@ -184,21 +185,14 @@ export async function getPaymentForm(inputInvoice: ApiRequestInputInvoice, theme } return buildApiPaymentForm(result); - } catch (err) { - if (err instanceof Error) { - // Can be removed if separate error handling is added to payment UI - sendApiUpdate({ - '@type': 'error', - error: { - message: err.message, - hasErrorKey: true, - }, - }); + } catch (err: any) { + if (err instanceof RPCError) { return { - error: err.message, + error: err.errorMessage, }; + } else { + throw err; } - return undefined; } } diff --git a/src/api/gramjs/methods/settings.ts b/src/api/gramjs/methods/settings.ts index 1fb36e8d9..c1ade1738 100644 --- a/src/api/gramjs/methods/settings.ts +++ b/src/api/gramjs/methods/settings.ts @@ -6,6 +6,7 @@ import type { LANG_PACKS } from '../../../config'; import type { ApiAppConfig, ApiConfig, + ApiDisallowedGiftsSettings, ApiInputPrivacyRules, ApiLanguage, ApiNotifyPeerType, @@ -24,6 +25,7 @@ import { import { buildCollectionByKey } from '../../../util/iteratees'; import { buildAppConfig } from '../apiBuilders/appConfig'; import { buildApiPhoto, buildPrivacyRules } from '../apiBuilders/common'; +import { buildApiDisallowedGiftsSettings } from '../apiBuilders/gifts'; import { buildApiConfig, buildApiCountryList, @@ -39,6 +41,7 @@ import { } from '../apiBuilders/misc'; import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers'; import { + buildDisallowedGiftsSettings, buildInputEntity, buildInputPeer, buildInputPhoto, buildInputPrivacyKey, buildInputPrivacyRules, @@ -657,6 +660,8 @@ export async function fetchGlobalPrivacySettings() { shouldHideReadMarks: Boolean(result.hideReadMarks), shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium), nonContactPeersPaidStars: Number(result.noncontactPeersPaidStars), + shouldDisplayGiftsButton: Boolean(result.displayGiftsButton), + disallowedGifts: result.disallowedGifts && buildApiDisallowedGiftsSettings(result.disallowedGifts), }; } @@ -665,18 +670,24 @@ export async function updateGlobalPrivacySettings({ shouldHideReadMarks, shouldNewNonContactPeersRequirePremium, nonContactPeersPaidStars, + shouldDisplayGiftsButton, + disallowedGifts, }: { shouldArchiveAndMuteNewNonContact?: boolean; shouldHideReadMarks?: boolean; shouldNewNonContactPeersRequirePremium?: boolean; nonContactPeersPaidStars?: number | null; + shouldDisplayGiftsButton?: boolean; + disallowedGifts?: ApiDisallowedGiftsSettings; }) { const result = await invokeRequest(new GramJs.account.SetGlobalPrivacySettings({ settings: new GramJs.GlobalPrivacySettings({ ...(shouldArchiveAndMuteNewNonContact && { archiveAndMuteNewNoncontactPeers: true }), ...(shouldHideReadMarks && { hideReadMarks: true }), ...(shouldNewNonContactPeersRequirePremium && { newNoncontactPeersRequirePremium: true }), + displayGiftsButton: shouldDisplayGiftsButton || undefined, noncontactPeersPaidStars: BigInt(nonContactPeersPaidStars || 0), + disallowedGifts: disallowedGifts && buildDisallowedGiftsSettings(disallowedGifts), }), })); @@ -689,6 +700,8 @@ export async function updateGlobalPrivacySettings({ shouldHideReadMarks: Boolean(result.hideReadMarks), shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium), nonContactPeersPaidStars: Number(result.noncontactPeersPaidStars), + shouldDisplayGiftsButton, + disallowedGifts, }; } diff --git a/src/api/types/stars.ts b/src/api/types/stars.ts index 01dfc111d..d0020465e 100644 --- a/src/api/types/stars.ts +++ b/src/api/types/stars.ts @@ -215,3 +215,10 @@ export interface ApiStarsGiveawayWinnerOption { users: number; perUserStars: number; } + +export interface ApiDisallowedGiftsSettings { + shouldDisallowUnlimitedStarGifts?: true; + shouldDisallowLimitedStarGifts?: true; + shouldDisallowUniqueStarGifts?: true; + shouldDisallowPremiumGifts?: true; +} diff --git a/src/api/types/users.ts b/src/api/types/users.ts index 281ef33ac..89549e4c7 100644 --- a/src/api/types/users.ts +++ b/src/api/types/users.ts @@ -55,6 +55,8 @@ export interface ApiUserFullInfo { areAdsEnabled?: boolean; hasPinnedStories?: boolean; isContactRequirePremium?: boolean; + shouldDisplayGiftsButton?: boolean; + disallowedGifts?: ApiDisallowedGifts; birthday?: ApiBirthday; personalChannelId?: string; personalChannelMessageId?: number; @@ -156,3 +158,10 @@ export interface ApiBirthday { month: number; year?: number; } + +export interface ApiDisallowedGifts { + shouldDisallowUnlimitedStarGifts?: boolean; + shouldDisallowLimitedStarGifts?: boolean; + shouldDisallowUniqueStarGifts?: boolean; + shouldDisallowPremiumGifts?: boolean; +} diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index 23eccd90e..5e44b030b 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -1564,7 +1564,28 @@ "PrivacyGifts" = "Gifts"; "PrivacyGiftsTitle" = "Who can display gifts on my profile?"; "PrivacyGiftsInfo" = "Choose whether gifts from specific senders need your approval before they're visible to others on your profile."; +"PrivacyAcceptedGiftTitle" = "Accepted gift types"; +"PrivacyAcceptedGiftInfo" = "Choose the types of gifts that you allow others to send you."; "PrivacyValueBots" = "Mini Apps"; +"PrivacyGiftLimitedEdition" = "Limited-Edition"; +"PrivacyGiftUnlimited" = "Unlimited"; +"PrivacyGiftUnique" = "Unique"; +"PrivacyGiftPremiumSubscription" = "Premium Subscription"; +"PrivacyDisplayGiftsButton" = "Show gift icon in chats"; +"PrivacyDisplayGift" = "Gift"; +"SendDisallowError" = "User is not accepting gifts"; +"PrivacyDisplayGiftIconInChats" = "Display the {icon} {gift} in the message input field for both participants in all chats."; +"PrivacySubscribeToTelegramPremium" = "Subscribe to **Telegram Premium** to select this option."; +"PrivacyDisableLimitedEditionStarGifts" = "Disable Limited-Edition Star Gifts"; +"PrivacyEnableLimitedEditionStarGifts" = "Enable Limited-Edition Star Gifts"; +"PrivacyDisableUnlimitedStarGifts" = "Disable Unlimited Star Gifts"; +"PrivacyEnableUnlimitedStarGifts" = "Enable Unlimited Star Gifts"; +"PrivacyDisableUniqueStarGifts" = "Disable Unique Star Gifts"; +"PrivacyEnableUniqueStarGifts" = "Enable Unique Star Gifts"; +"PrivacyDisablePremiumGifts" = "Disable Premium Gifts"; +"PrivacyEnablePremiumGifts" = "Enable Premium Gifts"; +"DisplayGiftsButton" = "Display Gifts Button"; +"HideGiftsButton" = "Hide Gifts Button"; "CustomShareGiftsInfo" = "You can add users or entire groups as exceptions that will override the settings above."; "StarsSubscribeBotText_one" = "Do you want to subscribe to **{name}** in **{bot}** for **{amount}** star per month?"; "StarsSubscribeBotText_other" = "Do you want to subscribe to **{name}** in **{bot}** for **{amount}** stars per month?"; diff --git a/src/components/common/Composer.tsx b/src/components/common/Composer.tsx index 2757c86ca..6a9153ae1 100644 --- a/src/components/common/Composer.tsx +++ b/src/components/common/Composer.tsx @@ -15,6 +15,7 @@ import type { ApiBotMenuButton, ApiChat, ApiChatFullInfo, + ApiDisallowedGifts, ApiDraft, ApiFormattedText, ApiMessage, @@ -227,6 +228,7 @@ type StateProps = isRightColumnShown?: boolean; isSelectModeActive?: boolean; isReactionPickerOpen?: boolean; + shouldDisplayGiftsButton?: boolean; isForwarding?: boolean; forwardedMessagesCount?: number; pollModal: TabState['pollModal']; @@ -292,6 +294,7 @@ type StateProps = isPaymentMessageConfirmDialogOpen: boolean; starsBalance: number; isStarsBalanceModalOpen: boolean; + disallowedGifts?: ApiDisallowedGifts; isAccountFrozen?: boolean; isAppConfigLoaded?: boolean; }; @@ -346,6 +349,7 @@ const Composer: FC = ({ isRightColumnShown, isSelectModeActive, isReactionPickerOpen, + shouldDisplayGiftsButton, isForwarding, forwardedMessagesCount, pollModal, @@ -414,6 +418,7 @@ const Composer: FC = ({ isPaymentMessageConfirmDialogOpen, starsBalance, isStarsBalanceModalOpen, + disallowedGifts, isAccountFrozen, isAppConfigLoaded, }) => { @@ -434,6 +439,7 @@ const Composer: FC = ({ showNotification, showAllowedMessageTypesNotification, openStoryReactionPicker, + openGiftModal, closeReactionPicker, sendStoryReaction, editMessage, @@ -835,6 +841,15 @@ const Composer: FC = ({ }; }, [chatId, threadId, resetComposerRef, stopRecordingVoiceRef]); + const areAllGiftsDisallowed = useMemo(() => { + if (!disallowedGifts) { + return undefined; + } + return Object.values(disallowedGifts).every(Boolean); + }, [disallowedGifts]); + + const shouldShowGiftButton = Boolean(!isChatWithSelf && shouldDisplayGiftsButton && !areAllGiftsDisallowed); + const showCustomEmojiPremiumNotification = useLastCallback(() => { const notificationNumber = customEmojiNotificationNumber.current; if (!notificationNumber) { @@ -1488,6 +1503,10 @@ const Composer: FC = ({ }); }); + const handleGiftClick = useLastCallback(() => { + openGiftModal({ forUserId: chatId }); + }); + const handleToggleSilentPosting = useLastCallback(() => { const newValue = !isSilentPosting; updateChatSilentPosting({ chatId, isEnabled: newValue }); @@ -2061,6 +2080,17 @@ const Composer: FC = ({ )} + {shouldShowGiftButton && ( + + )} {Boolean(botKeyboardMessageId) && !activeVoiceRecording && !editingMessage && ( ( isPaymentMessageConfirmDialogOpen: tabState.isPaymentMessageConfirmDialogOpen, starsBalance, isStarsBalanceModalOpen, + shouldDisplayGiftsButton: userFullInfo?.shouldDisplayGiftsButton, + disallowedGifts: userFullInfo?.disallowedGifts, isAccountFrozen, isAppConfigLoaded, }; diff --git a/src/components/left/settings/Settings.scss b/src/components/left/settings/Settings.scss index c0afda18e..b05ba3a2a 100644 --- a/src/components/left/settings/Settings.scss +++ b/src/components/left/settings/Settings.scss @@ -188,6 +188,10 @@ &[dir="rtl"] { text-align: right; } + + .gift-icon { + vertical-align: text-top; + } } .ListItem { diff --git a/src/components/left/settings/SettingsAcceptedGift.tsx b/src/components/left/settings/SettingsAcceptedGift.tsx new file mode 100644 index 000000000..bbab0e199 --- /dev/null +++ b/src/components/left/settings/SettingsAcceptedGift.tsx @@ -0,0 +1,158 @@ +import React, { memo } from '../../../lib/teact/teact'; +import { getActions, withGlobal } from '../../../global'; + +import type { ApiDisallowedGiftsSettings } from '../../../api/types'; + +import { selectIsCurrentUserPremium } from '../../../global/selectors'; + +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; + +import ListItem from '../../ui/ListItem'; +import Switcher from '../../ui/Switcher'; + +type StateProps = { + disallowedGifts?: ApiDisallowedGiftsSettings; + isCurrentUserPremium: boolean; +}; + +const SettingsAcceptedGift = ({ + disallowedGifts, isCurrentUserPremium, +}: StateProps) => { + const { showNotification, updateGlobalPrivacySettings } = getActions(); + + const lang = useLang(); + + const handleOpenTelegramPremiumModal = useLastCallback(() => { + showNotification({ + message: lang('PrivacySubscribeToTelegramPremium'), + action: { + action: 'openPremiumModal', + payload: {}, + }, + actionText: { key: 'Open' }, + icon: 'star', + }); + }); + + const handleLimitedEditionChange = useLastCallback(() => { + if (!isCurrentUserPremium) { + handleOpenTelegramPremiumModal(); + return; + } + + updateGlobalPrivacySettings({ + disallowedGifts: { + ...disallowedGifts, + shouldDisallowLimitedStarGifts: !disallowedGifts?.shouldDisallowLimitedStarGifts || undefined, + }, + }); + }); + + const handleUnlimitedEditionChange = useLastCallback(() => { + if (!isCurrentUserPremium) { + handleOpenTelegramPremiumModal(); + return; + } + updateGlobalPrivacySettings({ + disallowedGifts: { + ...disallowedGifts, + shouldDisallowUnlimitedStarGifts: !disallowedGifts?.shouldDisallowUnlimitedStarGifts || undefined, + }, + }); + }); + + const handleUniqueChange = useLastCallback(() => { + if (!isCurrentUserPremium) { + handleOpenTelegramPremiumModal(); + return; + } + updateGlobalPrivacySettings({ + disallowedGifts: { + ...disallowedGifts, + shouldDisallowUniqueStarGifts: !disallowedGifts?.shouldDisallowUniqueStarGifts || undefined, + }, + }); + }); + + const handlePremiumSubscriptionChange = useLastCallback(() => { + if (!isCurrentUserPremium) { + handleOpenTelegramPremiumModal(); + return; + } + updateGlobalPrivacySettings({ + disallowedGifts: { + ...disallowedGifts, + shouldDisallowPremiumGifts: !disallowedGifts?.shouldDisallowPremiumGifts || undefined, + }, + }); + }); + + return ( +
+

+ {lang('PrivacyAcceptedGiftTitle')} +

+ + {lang('PrivacyGiftLimitedEdition')} + + + + {lang('PrivacyGiftUnlimited')} + + + + {lang('PrivacyGiftUnique')} + + + + {lang('PrivacyGiftPremiumSubscription')} + + +

+ {lang('PrivacyAcceptedGiftInfo')} +

+
+ ); +}; + +export default memo(withGlobal( + (global): StateProps => { + const { + settings: { + byKey: { + disallowedGifts, + }, + }, + } = global; + + return { + disallowedGifts, + isCurrentUserPremium: selectIsCurrentUserPremium(global), + }; + }, +)(SettingsAcceptedGift)); diff --git a/src/components/left/settings/SettingsPrivacyVisibility.tsx b/src/components/left/settings/SettingsPrivacyVisibility.tsx index e4bda6f00..274c0fa69 100644 --- a/src/components/left/settings/SettingsPrivacyVisibility.tsx +++ b/src/components/left/settings/SettingsPrivacyVisibility.tsx @@ -13,10 +13,13 @@ import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; import useOldLang from '../../../hooks/useOldLang'; +import Icon from '../../common/icons/Icon'; import ListItem from '../../ui/ListItem'; import RadioGroup from '../../ui/RadioGroup'; +import Switcher from '../../ui/Switcher'; import PremiumStatusItem from './PremiumStatusItem'; import PrivacyLockedOption from './PrivacyLockedOption'; +import SettingsAcceptedGift from './SettingsAcceptedGift'; import SettingsPrivacyLastSeen from './SettingsPrivacyLastSeen'; import SettingsPrivacyPublicProfilePhoto from './SettingsPrivacyPublicProfilePhoto'; @@ -34,6 +37,8 @@ type StateProps = { primaryPrivacy?: ApiPrivacySettings; secondaryPrivacy?: ApiPrivacySettings; isPremiumRequired?: boolean; + shouldDisplayGiftsButton?: boolean; + isCurrentUserPremium?: boolean; }; const SettingsPrivacyVisibility: FC = ({ @@ -47,12 +52,36 @@ const SettingsPrivacyVisibility: FC = ({ isPremiumRequired, onScreenSelect, onReset, + shouldDisplayGiftsButton, + isCurrentUserPremium, }) => { + const lang = useLang(); + + const { updateGlobalPrivacySettings, showNotification } = getActions(); + useHistoryBack({ isActive, onBack: onReset, }); + const handleShowGiftIconInChats = useLastCallback(() => { + if (!isCurrentUserPremium) { + showNotification({ + message: lang('PrivacySubscribeToTelegramPremium'), + action: { + action: 'openPremiumModal', + payload: {}, + }, + actionText: { key: 'Open' }, + icon: 'star', + }); + return; + } + updateGlobalPrivacySettings({ + shouldDisplayGiftsButton: !shouldDisplayGiftsButton, + }); + }); + const secondaryScreen = useMemo(() => { switch (screen) { case SettingsScreens.PrivacyPhoneCall: @@ -67,6 +96,27 @@ const SettingsPrivacyVisibility: FC = ({ return (
+ {screen === SettingsScreens.PrivacyGifts && ( +
+ + {lang('PrivacyDisplayGiftsButton')} + + +

+ {lang('PrivacyDisplayGiftIconInChats', { + icon: , + gift: lang('PrivacyDisplayGift'), + }, { + withNodes: true, + })} +

+
+ )} = ({ {screen === SettingsScreens.PrivacyLastSeen && ( )} + {screen === SettingsScreens.PrivacyGifts && ( + + )} {secondaryScreen && ( ( const { currentUserId, - settings: { privacy }, + settings: { + privacy, + byKey: { + shouldDisplayGiftsButton, + }, + }, } = global; const currentUserFullInfo = selectUserFullInfo(global, currentUserId!); @@ -426,6 +484,8 @@ export default memo(withGlobal( hasCurrentUserFullInfo: Boolean(currentUserFullInfo), currentUserFallbackPhoto: currentUserFullInfo?.fallbackPhoto, isPremiumRequired: screen === SettingsScreens.PrivacyVoiceMessages && !selectIsCurrentUserPremium(global), + shouldDisplayGiftsButton, + isCurrentUserPremium: selectIsCurrentUserPremium(global), }; }, )(SettingsPrivacyVisibility)); diff --git a/src/components/middle/HeaderMenuContainer.tsx b/src/components/middle/HeaderMenuContainer.tsx index 56bba3dac..c4b38555f 100644 --- a/src/components/middle/HeaderMenuContainer.tsx +++ b/src/components/middle/HeaderMenuContainer.tsx @@ -4,7 +4,9 @@ import React, { } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; -import type { ApiBotCommand, ApiChat } from '../../api/types'; +import type { + ApiBotCommand, ApiChat, ApiDisallowedGifts, +} from '../../api/types'; import type { IAnchorPosition, ThreadId } from '../../types'; import type { IconName } from '../../types/icons'; import { MAIN_THREAD_ID } from '../../api/types'; @@ -22,6 +24,7 @@ import { isUserRightBanned, } from '../../global/helpers'; import { getIsChatMuted } from '../../global/helpers/notifications'; +import { getPeerFullTitle } from '../../global/helpers/peers'; import { selectBot, selectCanGift, @@ -44,6 +47,7 @@ import { disableScrolling } from '../../util/scrollLock'; import useAppLayout from '../../hooks/useAppLayout'; import useFlag from '../../hooks/useFlag'; +import useLang from '../../hooks/useLang'; import useLastCallback from '../../hooks/useLastCallback'; import useOldLang from '../../hooks/useOldLang'; import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation'; @@ -123,6 +127,7 @@ type StateProps = { isBot?: boolean; isChatWithSelf?: boolean; savedDialog?: ApiChat; + disallowedGifts?: ApiDisallowedGifts; isAccountFrozen?: boolean; }; @@ -178,6 +183,7 @@ const HeaderMenuContainer: FC = ({ onAsMessagesClick, onClose, onCloseAnimationEnd, + disallowedGifts, isAccountFrozen, }) => { const { @@ -207,8 +213,12 @@ const HeaderMenuContainer: FC = ({ setViewForumAsMessages, openBoostModal, reportMessages, + showNotification, } = getActions(); + const oldLang = useOldLang(); + const lang = useLang(); + const { isMobile } = useAppLayout(); const [isMenuOpen, setIsMenuOpen] = useState(true); const [shouldCloseFast, setShouldCloseFast] = useState(false); @@ -222,6 +232,13 @@ const HeaderMenuContainer: FC = ({ (!isChatInfoShown && isForum) ? true : undefined, CLOSE_MENU_ANIMATION_DURATION, ); + const areAllGiftsDisallowed = useMemo(() => { + if (!disallowedGifts) { + return undefined; + } + return Object.values(disallowedGifts).every(Boolean); + }, [disallowedGifts]); + const closeMuteModal = useLastCallback(() => { setIsMuteModalOpen(false); onClose(); @@ -357,6 +374,11 @@ const HeaderMenuContainer: FC = ({ }); const handleGiftClick = useLastCallback(() => { + if (areAllGiftsDisallowed && chat) { + showNotification({ message: lang('SendDisallowError') }); + return; + } + openGiftModal({ forUserId: chatId }); if (isAccountFrozen) { openFrozenAccountModal(); } else { @@ -469,8 +491,6 @@ const HeaderMenuContainer: FC = ({ useEffect(disableScrolling, []); - const lang = useOldLang(); - const botButtons = useMemo(() => { const commandButtons = botCommands?.map(({ command }) => { const cmd = BOT_BUTTONS[command]; @@ -488,7 +508,7 @@ const HeaderMenuContainer: FC = ({ // eslint-disable-next-line react/jsx-no-bind onClick={handleClick} > - {lang(cmd.label)} + {oldLang(cmd.label)} ); }); @@ -503,39 +523,39 @@ const HeaderMenuContainer: FC = ({ if (hasPrivacyCommand && !botPrivacyPolicyUrl) { sendBotCommand({ command: '/privacy' }); } else { - openUrl({ url: botPrivacyPolicyUrl || lang('BotDefaultPrivacyPolicy') }); + openUrl({ url: botPrivacyPolicyUrl || oldLang('BotDefaultPrivacyPolicy') }); } closeMenu(); }} > - {lang('BotPrivacyPolicy')} + {oldLang('BotPrivacyPolicy')} ); return [...commandButtons || [], privacyButton].filter(Boolean); - }, [botCommands, lang, botPrivacyPolicyUrl, isBot]); + }, [botCommands, oldLang, botPrivacyPolicyUrl, isBot]); const deleteTitle = useMemo(() => { if (!chat) return undefined; if (savedDialog) { - return lang('Delete'); + return oldLang('Delete'); } if (isPrivate) { - return lang('DeleteChatUser'); + return oldLang('DeleteChatUser'); } if (canDeleteChat) { - return lang('GroupInfo.DeleteAndExit'); + return oldLang('GroupInfo.DeleteAndExit'); } if (isChannel) { - return lang('LeaveChannel'); + return oldLang('LeaveChannel'); } - return lang('Group.LeaveGroup'); - }, [canDeleteChat, chat, isChannel, isPrivate, savedDialog, lang]); + return oldLang('Group.LeaveGroup'); + }, [canDeleteChat, chat, isChannel, isPrivate, savedDialog, oldLang]); return ( @@ -552,7 +572,7 @@ const HeaderMenuContainer: FC = ({ icon="search" onClick={handleSearch} > - {lang('Search')} + {oldLang('Search')} )} {withForumActions && canCreateTopic && ( @@ -561,7 +581,7 @@ const HeaderMenuContainer: FC = ({ icon="comments" onClick={handleCreateTopicClick} > - {lang('lng_forum_create_topic')} + {oldLang('lng_forum_create_topic')} @@ -571,7 +591,7 @@ const HeaderMenuContainer: FC = ({ icon="info" onClick={handleViewGroupInfo} > - {isTopic ? lang('lng_context_view_topic') : lang('lng_context_view_group')} + {isTopic ? oldLang('lng_context_view_topic') : oldLang('lng_context_view_group')} )} {canManage && !canEditTopic && ( @@ -579,7 +599,7 @@ const HeaderMenuContainer: FC = ({ icon="edit" onClick={handleEditClick} > - {lang('Edit')} + {oldLang('Edit')} )} {canEditTopic && ( @@ -587,7 +607,7 @@ const HeaderMenuContainer: FC = ({ icon="edit" onClick={handleEditTopicClick} > - {lang('lng_forum_topic_edit')} + {oldLang('lng_forum_topic_edit')} )} {isMobile && !withForumActions && isForum && !isTopic && ( @@ -595,7 +615,7 @@ const HeaderMenuContainer: FC = ({ icon="forums" onClick={handleViewAsTopicsClick} > - {lang('Chat.ContextViewAsTopics')} + {oldLang('Chat.ContextViewAsTopics')} )} {withForumActions && Boolean(pendingJoinRequests) && ( @@ -603,7 +623,7 @@ const HeaderMenuContainer: FC = ({ icon="user" onClick={onJoinRequestsClick} > - {isChannel ? lang('SubscribeRequests') : lang('MemberRequests')} + {isChannel ? oldLang('SubscribeRequests') : oldLang('MemberRequests')}
{pendingJoinRequests}
)} @@ -612,7 +632,7 @@ const HeaderMenuContainer: FC = ({ icon="message" onClick={handleOpenAsMessages} > - {lang('lng_forum_view_as_messages')} + {oldLang('lng_forum_view_as_messages')} )} {withExtraActions && canStartBot && ( @@ -620,7 +640,7 @@ const HeaderMenuContainer: FC = ({ icon="bots" onClick={handleStartBot} > - {lang('BotStart')} + {oldLang('BotStart')} )} {withExtraActions && canSubscribe && ( @@ -628,7 +648,7 @@ const HeaderMenuContainer: FC = ({ icon={isChannel ? 'channel' : 'group'} onClick={handleSubscribe} > - {lang(isChannel ? 'ProfileJoinChannel' : 'ProfileJoinGroup')} + {oldLang(isChannel ? 'ProfileJoinChannel' : 'ProfileJoinGroup')} )} {canShowBoostModal && !canViewBoosts && ( @@ -636,7 +656,7 @@ const HeaderMenuContainer: FC = ({ icon="boost-outline" onClick={handleBoostClick} > - {lang(isChannel ? 'BoostingBoostChannelMenu' : 'BoostingBoostGroupMenu')} + {oldLang(isChannel ? 'BoostingBoostChannelMenu' : 'BoostingBoostGroupMenu')} )} {canAddContact && ( @@ -644,7 +664,7 @@ const HeaderMenuContainer: FC = ({ icon="add-user" onClick={handleAddContactClick} > - {lang('AddContact')} + {oldLang('AddContact')} )} {isMobile && canCall && ( @@ -652,7 +672,7 @@ const HeaderMenuContainer: FC = ({ icon="phone" onClick={handleCall} > - {lang('Call')} + {oldLang('Call')} )} {canCall && ( @@ -660,7 +680,7 @@ const HeaderMenuContainer: FC = ({ icon="video-outlined" onClick={handleVideoCall} > - {lang('VideoCall')} + {oldLang('VideoCall')} )} {canMute && (isMuted ? ( @@ -668,7 +688,7 @@ const HeaderMenuContainer: FC = ({ icon="unmute" onClick={handleUnmuteClick} > - {lang('ChatsUnmute')} + {oldLang('ChatsUnmute')} ) : ( @@ -676,7 +696,7 @@ const HeaderMenuContainer: FC = ({ icon="mute" onClick={handleMuteClick} > - {lang('ChatsMute')}... + {oldLang('ChatsMute')}... ) )} @@ -685,7 +705,7 @@ const HeaderMenuContainer: FC = ({ icon="voice-chat" onClick={handleEnterVoiceChatClick} > - {lang(canCreateVoiceChat ? 'StartVoipChat' : 'VoipGroupJoinCall')} + {oldLang(canCreateVoiceChat ? 'StartVoipChat' : 'VoipGroupJoinCall')} )} {hasLinkedChat && ( @@ -693,7 +713,7 @@ const HeaderMenuContainer: FC = ({ icon={isChannel ? 'comments' : 'channel'} onClick={handleLinkedChatClick} > - {lang(isChannel ? 'ViewDiscussion' : 'lng_profile_view_channel')} + {oldLang(isChannel ? 'ViewDiscussion' : 'lng_profile_view_channel')} )} {!withForumActions && ( @@ -701,7 +721,7 @@ const HeaderMenuContainer: FC = ({ icon="select" onClick={handleSelectMessages} > - {lang('ReportSelectMessages')} + {oldLang('ReportSelectMessages')} )} {canViewBoosts && ( @@ -709,7 +729,7 @@ const HeaderMenuContainer: FC = ({ icon="boost-outline" onClick={handleBoostClick} > - {lang('Boosts')} + {oldLang('Boosts')} )} {canViewStatistics && ( @@ -717,7 +737,7 @@ const HeaderMenuContainer: FC = ({ icon="stats" onClick={handleStatisticsClick} > - {lang('Statistics')} + {oldLang('Statistics')} )} {isChannel && canViewMonetization && ( @@ -725,7 +745,7 @@ const HeaderMenuContainer: FC = ({ icon="cash-circle" onClick={handleMonetizationClick} > - {lang('lng_channel_earn_title')} + {oldLang('lng_channel_earn_title')} )} {canTranslate && ( @@ -733,7 +753,7 @@ const HeaderMenuContainer: FC = ({ icon="language" onClick={handleEnableTranslations} > - {lang('lng_context_translate')} + {oldLang('lng_context_translate')} )} {canReportChat && ( @@ -741,7 +761,7 @@ const HeaderMenuContainer: FC = ({ icon="flag" onClick={handleReport} > - {lang('ReportPeer.Report')} + {oldLang('ReportPeer.Report')} )} {botButtons} @@ -750,7 +770,7 @@ const HeaderMenuContainer: FC = ({ icon="gift" onClick={handleGiftClick} > - {lang('ProfileSendAGift')} + {oldLang('ProfileSendAGift')} )} {isBot && ( @@ -758,7 +778,7 @@ const HeaderMenuContainer: FC = ({ icon={isBlocked ? 'bots' : 'hand-stop'} onClick={isBlocked ? handleRestartBot : handleBlock} > - {isBlocked ? lang('BotRestart') : lang('Bot.Stop')} + {isBlocked ? oldLang('BotRestart') : oldLang('Bot.Stop')} )} {isPrivate && !isChatWithSelf && !isBot && ( @@ -766,7 +786,7 @@ const HeaderMenuContainer: FC = ({ icon={isBlocked ? 'user' : 'hand-stop'} onClick={isBlocked ? handleUnblock : handleBlock} > - {isBlocked ? lang('Unblock') : lang('BlockUser')} + {isBlocked ? oldLang('Unblock') : oldLang('BlockUser')} )} {canLeave && ( @@ -861,6 +881,7 @@ export default memo(withGlobal( isBot: Boolean(chatBot), isChatWithSelf, savedDialog, + disallowedGifts: userFullInfo?.disallowedGifts, isAccountFrozen, }; }, diff --git a/src/components/modals/gift/GiftComposer.tsx b/src/components/modals/gift/GiftComposer.tsx index c141c1f1c..a87538dab 100644 --- a/src/components/modals/gift/GiftComposer.tsx +++ b/src/components/modals/gift/GiftComposer.tsx @@ -1,6 +1,6 @@ import type { ChangeEvent } from 'react'; import React, { - memo, useMemo, useState, + memo, useEffect, useMemo, useState, } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; @@ -10,11 +10,9 @@ import { type ApiMessage, type ApiPeer, type ApiStarsAmount, MAIN_THREAD_ID, } from '../../../api/types'; -import { -} from '../../../global/helpers'; import { getPeerTitle, isApiPeerUser } from '../../../global/helpers/peers'; import { - selectPeer, selectPeerPaidMessagesStars, selectTabState, selectTheme, selectThemeValues, + selectPeer, selectPeerPaidMessagesStars, selectTabState, selectTheme, selectThemeValues, selectUserFullInfo, } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import buildStyle from '../../../util/buildStyle'; @@ -53,6 +51,8 @@ export type StateProps = { isPaymentFormLoading?: boolean; starBalance?: ApiStarsAmount; paidMessagesStars?: number; + areUniqueStarGiftsDisallowed?: boolean; + shouldDisallowLimitedStarGifts?: boolean; }; const LIMIT_DISPLAY_THRESHOLD = 50; @@ -72,6 +72,8 @@ function GiftComposer({ isPaymentFormLoading, starBalance, paidMessagesStars, + areUniqueStarGiftsDisallowed, + shouldDisallowLimitedStarGifts, }: OwnProps & StateProps) { const { sendStarGift, sendPremiumGiftByStars, openInvoice, openGiftUpgradeModal, openStarsBalanceModal, @@ -86,6 +88,12 @@ function GiftComposer({ const customBackgroundValue = useCustomBackground(theme, customBackground); + useEffect(() => { + if (shouldDisallowLimitedStarGifts) { + setShouldPayForUpgrade(true); + } + }, [shouldDisallowLimitedStarGifts, shouldPayForUpgrade]); + const isStarGift = 'id' in gift; const hasPremiumByStars = giftByStars && 'amount' in giftByStars; const isPeerUser = peer && isApiPeerUser(peer); @@ -248,8 +256,14 @@ function GiftComposer({
)} - {isStarGift && gift.upgradeStars && ( - + {isStarGift && gift.upgradeStars && !areUniqueStarGiftsDisallowed && ( + {lang('GiftMakeUnique', { stars: formatStarsAsIcon(lang, gift.upgradeStars, { className: styles.switcherStarIcon }), @@ -262,7 +276,7 @@ function GiftComposer({ /> )} - {isStarGift && gift.upgradeStars && ( + {isStarGift && gift.upgradeStars && !areUniqueStarGiftsDisallowed && (
{isPeerUser ? lang('GiftMakeUniqueDescription', { @@ -388,6 +402,13 @@ export default memo(withGlobal( } = selectThemeValues(global, theme) || {}; const peer = selectPeer(global, peerId); const paidMessagesStars = selectPeerPaidMessagesStars(global, peerId); + const userFullInfo = selectUserFullInfo(global, peerId); + const currentUserId = global.currentUserId; + const isGiftForSelf = currentUserId === peerId; + const areUniqueStarGiftsDisallowed = !isGiftForSelf + && userFullInfo?.disallowedGifts?.shouldDisallowUniqueStarGifts; + const shouldDisallowLimitedStarGifts = !isGiftForSelf + && userFullInfo?.disallowedGifts?.shouldDisallowLimitedStarGifts; const tabState = selectTabState(global); @@ -403,6 +424,8 @@ export default memo(withGlobal( currentUserId: global.currentUserId, isPaymentFormLoading: tabState.isPaymentFormLoading, paidMessagesStars, + areUniqueStarGiftsDisallowed, + shouldDisallowLimitedStarGifts, }; }, )(GiftComposer)); diff --git a/src/components/modals/gift/GiftModal.tsx b/src/components/modals/gift/GiftModal.tsx index bbf4387f1..e09e554ba 100644 --- a/src/components/modals/gift/GiftModal.tsx +++ b/src/components/modals/gift/GiftModal.tsx @@ -5,6 +5,7 @@ import React, { import { getActions, withGlobal } from '../../../global'; import type { + ApiDisallowedGifts, ApiPeer, ApiPremiumGiftCodeOption, ApiStarGiftRegular, @@ -16,7 +17,7 @@ import type { StarGiftCategory } from '../../../types'; import { STARS_CURRENCY_CODE } from '../../../config'; import { getUserFullName } from '../../../global/helpers'; import { getPeerTitle, isApiPeerChat, isApiPeerUser } from '../../../global/helpers/peers'; -import { selectPeer } from '../../../global/selectors'; +import { selectPeer, selectUserFullInfo } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import { throttle } from '../../../util/schedulers'; @@ -54,6 +55,7 @@ type StateProps = { starBalance?: ApiStarsAmount; peer?: ApiPeer; isSelf?: boolean; + disallowedGifts?: ApiDisallowedGifts; }; const AVATAR_SIZE = 100; @@ -69,6 +71,7 @@ const GiftModal: FC = ({ starBalance, peer, isSelf, + disallowedGifts, }) => { const { closeGiftModal, @@ -96,6 +99,20 @@ const GiftModal: FC = ({ const [selectedCategory, setSelectedCategory] = useState('all'); + const areAllGiftsDisallowed = useMemo(() => { + if (!disallowedGifts) { + return undefined; + } + const { + shouldDisallowPremiumGifts, + ...disallowedGiftTypes + } = disallowedGifts; + return !isSelf && Object.values(disallowedGiftTypes).every(Boolean); + }, [isSelf, disallowedGifts]); + + const areUnlimitedStarGiftsDisallowed = !isSelf && disallowedGifts?.shouldDisallowUnlimitedStarGifts; + const areLimitedStarGiftsDisallowed = !isSelf && disallowedGifts?.shouldDisallowLimitedStarGifts; + const oldLang = useOldLang(); const lang = useLang(); const allGifts = renderingModal?.gifts; @@ -221,12 +238,31 @@ const GiftModal: FC = ({ }); function renderStarGifts() { + const filteredGiftIds = starGiftIdsByCategory?.[selectedCategory]?.filter((giftId) => { + const gift = starGiftsById?.[giftId]; + if (!gift) return false; + + const { isLimited, isSoldOut, upgradeStars } = gift; + if (areUnlimitedStarGiftsDisallowed && !areLimitedStarGiftsDisallowed) { + return isLimited; + } + if (areLimitedStarGiftsDisallowed && !areUnlimitedStarGiftsDisallowed) { + return !isLimited && !isSoldOut; + } + if (areUnlimitedStarGiftsDisallowed && areLimitedStarGiftsDisallowed) { + return Boolean(isLimited && !!upgradeStars); + } + + return true; + }); + return (
- {starGiftsById && starGiftIdsByCategory?.[selectedCategory].map((giftId) => { + {starGiftsById && filteredGiftIds?.map((giftId) => { const gift = starGiftsById[giftId]; return ( = ({ />
- {!isSelf && !chat && renderGiftPremiumHeader()} - {!isSelf && !chat && renderGiftPremiumDescription()} - {!isSelf && !chat && renderPremiumGifts()} + {!isSelf && !chat && !disallowedGifts?.shouldDisallowPremiumGifts && ( + <> + {renderGiftPremiumHeader()} + {renderGiftPremiumDescription()} + {renderPremiumGifts()} + + )} - {renderStarGiftsHeader()} - {renderStarGiftsDescription()} - - - {renderStarGifts()} - + {!areAllGiftsDisallowed && ( + <> + {renderStarGiftsHeader()} + {renderStarGiftsDescription()} + + + {renderStarGifts()} + + + )}
); } @@ -361,6 +408,7 @@ export default memo(withGlobal((global, { modal }): StateProps => { const peer = modal?.forPeerId ? selectPeer(global, modal.forPeerId) : undefined; const isSelf = Boolean(currentUserId && modal?.forPeerId === currentUserId); + const userFullInfo = peer ? selectUserFullInfo(global, peer?.id) : undefined; return { boostPerSentGift: global.appConfig?.boostsPerSentGift, @@ -369,6 +417,7 @@ export default memo(withGlobal((global, { modal }): StateProps => { starBalance: stars?.balance, peer, isSelf, + disallowedGifts: userFullInfo?.disallowedGifts, }; })(GiftModal)); diff --git a/src/components/modals/gift/StarGiftCategoryList.tsx b/src/components/modals/gift/StarGiftCategoryList.tsx index e48861a6a..2dfa08f9a 100644 --- a/src/components/modals/gift/StarGiftCategoryList.tsx +++ b/src/components/modals/gift/StarGiftCategoryList.tsx @@ -20,11 +20,13 @@ type OwnProps = { type StateProps = { idsByCategory?: Record; + areLimitedStarGiftsDisallowed?: boolean; }; const StarGiftCategoryList = ({ idsByCategory, onCategoryChanged, + areLimitedStarGiftsDisallowed, }: StateProps & OwnProps) => { // eslint-disable-next-line no-null/no-null const ref = useRef(null); @@ -78,7 +80,7 @@ const StarGiftCategoryList = ({ return (
{renderCategoryItem('all')} - {renderCategoryItem('limited')} + {!areLimitedStarGiftsDisallowed && renderCategoryItem('limited')} {renderCategoryItem('stock')} {starCategories?.map(renderCategoryItem)}
diff --git a/src/global/actions/api/payments.ts b/src/global/actions/api/payments.ts index e821e1a4e..21ec81352 100644 --- a/src/global/actions/api/payments.ts +++ b/src/global/actions/api/payments.ts @@ -1099,6 +1099,7 @@ async function payInputStarInvoice( setGlobal(global); if ('error' in form) { + actions.showDialog({ data: { message: form.error || 'Error', hasErrorKey: true }, tabId }); return; } diff --git a/src/global/actions/api/settings.ts b/src/global/actions/api/settings.ts index 1d7429693..bfb000662 100644 --- a/src/global/actions/api/settings.ts +++ b/src/global/actions/api/settings.ts @@ -725,6 +725,10 @@ addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload) // eslint-disable-next-line no-null/no-null const nonContactPeersPaidStars = payload.nonContactPeersPaidStars === null ? undefined : payload.nonContactPeersPaidStars || global.settings.byKey.nonContactPeersPaidStars; + const shouldDisplayGiftsButton = payload.shouldDisplayGiftsButton + ?? Boolean(global.settings.byKey.shouldDisplayGiftsButton); + const disallowedGifts = payload.disallowedGifts + ?? global.settings.byKey.disallowedGifts; // eslint-disable-next-line no-null/no-null const shouldUpdateUsersSettings = (payload.nonContactPeersPaidStars === null) @@ -736,6 +740,8 @@ addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload) shouldHideReadMarks, shouldNewNonContactPeersRequirePremium, nonContactPeersPaidStars, + shouldDisplayGiftsButton, + disallowedGifts, }); setGlobal(global); @@ -744,6 +750,8 @@ addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload) shouldHideReadMarks, shouldNewNonContactPeersRequirePremium, nonContactPeersPaidStars, + shouldDisplayGiftsButton, + disallowedGifts, }); global = getGlobal(); @@ -758,6 +766,8 @@ addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload) nonContactPeersPaidStars: !result ? undefined : result.nonContactPeersPaidStars, + shouldDisplayGiftsButton: !result ? !shouldDisplayGiftsButton : result.shouldDisplayGiftsButton, + disallowedGifts: !result ? disallowedGifts : result.disallowedGifts, }); if (shouldUpdateUsersSettings) { diff --git a/src/global/initialState.ts b/src/global/initialState.ts index 7f8b424d3..7cb383c28 100644 --- a/src/global/initialState.ts +++ b/src/global/initialState.ts @@ -299,6 +299,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = { shouldUpdateStickerSetOrder: true, shouldArchiveAndMuteNewNonContact: false, shouldNewNonContactPeersRequirePremium: false, + disallowedGifts: undefined, nonContactPeersPaidStars: 0, shouldHideReadMarks: false, canTranslate: false, diff --git a/src/global/types/actions.ts b/src/global/types/actions.ts index 23a479782..f20715f20 100644 --- a/src/global/types/actions.ts +++ b/src/global/types/actions.ts @@ -8,6 +8,7 @@ import type { ApiChatlistInvite, ApiChatReactions, ApiChatType, + ApiDisallowedGiftsSettings, ApiDraft, ApiExportedInvite, ApiFormattedText, @@ -2329,6 +2330,8 @@ export interface ActionPayloads { shouldHideReadMarks?: boolean; shouldNewNonContactPeersRequirePremium?: boolean; nonContactPeersPaidStars?: number | null; + shouldDisplayGiftsButton?: boolean; + disallowedGifts?: ApiDisallowedGiftsSettings; }; // Premium diff --git a/src/types/index.ts b/src/types/index.ts index 19be7333e..a4e035ac7 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -9,6 +9,7 @@ import type { ApiChat, ApiChatInviteImporter, ApiContact, + ApiDisallowedGiftsSettings, ApiDocument, ApiDraft, ApiExportedInvite, @@ -147,6 +148,8 @@ export interface AccountSettings { shouldArchiveAndMuteNewNonContact?: boolean; shouldNewNonContactPeersRequirePremium?: boolean; nonContactPeersPaidStars?: number; + shouldDisplayGiftsButton?: boolean; + disallowedGifts?: ApiDisallowedGiftsSettings; shouldHideReadMarks?: boolean; canTranslate: boolean; canTranslateChats: boolean; diff --git a/src/types/language.d.ts b/src/types/language.d.ts index 611e964a9..5cdbe58ef 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -1282,7 +1282,16 @@ export interface LangPair { 'PrivacyGifts': undefined; 'PrivacyGiftsTitle': undefined; 'PrivacyGiftsInfo': undefined; + 'PrivacyAcceptedGiftTitle': undefined; + 'PrivacyAcceptedGiftInfo': undefined; + 'PrivacyDisplayGiftsButton': undefined; + 'PrivacyDisplayGift': undefined; + 'SendDisallowError': undefined; 'PrivacyValueBots': undefined; + 'PrivacyGiftLimitedEdition': undefined; + 'PrivacyGiftUnlimited': undefined; + 'PrivacyGiftUnique': undefined; + 'PrivacyGiftPremiumSubscription': undefined; 'CustomShareGiftsInfo': undefined; 'AllChatsSearchContext': undefined; 'PrivateChatsSearchContext': undefined; @@ -1466,6 +1475,17 @@ export interface LangPair { 'DescriptionRestrictedMedia': undefined; 'DescriptionScheduledPaidMediaNotAllowed': undefined; 'DescriptionScheduledPaidMessagesNotAllowed': undefined; + 'PrivacySubscribeToTelegramPremium': undefined; + 'PrivacyDisableLimitedEditionStarGifts': undefined; + 'PrivacyEnableLimitedEditionStarGifts': undefined; + 'PrivacyDisableUnlimitedStarGifts': undefined; + 'PrivacyEnableUnlimitedStarGifts': undefined; + 'PrivacyDisableUniqueStarGifts': undefined; + 'PrivacyEnableUniqueStarGifts': undefined; + 'PrivacyDisablePremiumGifts': undefined; + 'PrivacyEnablePremiumGifts': undefined; + 'DisplayGiftsButton': undefined; + 'HideGiftsButton': undefined; 'FrozenAccountModalTitle': undefined; 'FrozenAccountViolationTitle': undefined; 'FrozenAccountViolationSubtitle': undefined; @@ -1555,6 +1575,10 @@ export interface LangPairWithVariables { 'SpeakingWithVolume': { 'volume': V; }; + 'PrivacyDisplayGiftIconInChats':{ + 'icon': V; + 'gift': V; + }; 'CallEmojiKeyTooltip': { 'user': V; }; diff --git a/src/util/getReadableErrorText.ts b/src/util/getReadableErrorText.ts index 6a4a0dd05..2ce55d302 100644 --- a/src/util/getReadableErrorText.ts +++ b/src/util/getReadableErrorText.ts @@ -81,6 +81,8 @@ const READABLE_ERROR_MESSAGES: Record = { PEERS_LIST_EMPTY: 'No chats are added to the list', PAID_MEDIA_FORBIDDEN: 'You can\'t send paid media in this chat', + + USER_DISALLOWED_STARGIFTS: 'User is not accepting gifts', }; if (DEBUG) {