Settings: Implement settings privacy for gifts (#5802)

This commit is contained in:
Alexander Zinchuk 2025-04-23 18:59:27 +02:00
parent 97bd26df00
commit 05ff8a385b
23 changed files with 546 additions and 77 deletions

View File

@ -1,6 +1,7 @@
import { Api as GramJs } from '../../../lib/gramjs'; import { Api as GramJs } from '../../../lib/gramjs';
import type { import type {
ApiDisallowedGiftsSettings,
ApiInputSavedStarGift, ApiInputSavedStarGift,
ApiSavedStarGift, ApiSavedStarGift,
ApiStarGift, ApiStarGift,
@ -156,3 +157,21 @@ export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId
isPinned: pinnedToTop, isPinned: pinnedToTop,
}; };
} }
export function buildApiDisallowedGiftsSettings(
result: GramJs.TypeDisallowedGiftsSettings,
): ApiDisallowedGiftsSettings {
const {
disallowUnlimitedStargifts,
disallowLimitedStargifts,
disallowUniqueStargifts,
disallowPremiumGifts,
} = result;
return {
shouldDisallowUnlimitedStarGifts: disallowUnlimitedStargifts,
shouldDisallowLimitedStarGifts: disallowLimitedStargifts,
shouldDisallowUniqueStarGifts: disallowUniqueStargifts,
shouldDisallowPremiumGifts: disallowPremiumGifts,
};
}

View File

@ -14,6 +14,7 @@ import { buildApiBusinessIntro, buildApiBusinessLocation, buildApiBusinessWorkHo
import { import {
buildApiBotVerification, buildApiPhoto, buildApiUsernames, buildAvatarPhotoId, buildApiBotVerification, buildApiPhoto, buildApiUsernames, buildAvatarPhotoId,
} from './common'; } from './common';
import { buildApiDisallowedGiftsSettings } from './gifts';
import { omitVirtualClassFields } from './helpers'; import { omitVirtualClassFields } from './helpers';
import { buildApiEmojiStatus, buildApiPeerColor, buildApiPeerId } from './peers'; import { buildApiEmojiStatus, buildApiPeerColor, buildApiPeerId } from './peers';
@ -25,7 +26,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
fallbackPhoto, personalPhoto, translationsDisabled, storiesPinnedAvailable, fallbackPhoto, personalPhoto, translationsDisabled, storiesPinnedAvailable,
contactRequirePremium, businessWorkHours, businessLocation, businessIntro, contactRequirePremium, businessWorkHours, businessLocation, businessIntro,
birthday, personalChannelId, personalChannelMessage, sponsoredEnabled, stargiftsCount, botVerification, birthday, personalChannelId, personalChannelMessage, sponsoredEnabled, stargiftsCount, botVerification,
botCanManageEmojiStatus, settings, sendPaidMessagesStars, botCanManageEmojiStatus, settings, sendPaidMessagesStars, displayGiftsButton, disallowedGifts,
}, },
users, users,
} = mtpUserFull; } = mtpUserFull;
@ -45,6 +46,8 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
personalPhoto: personalPhoto instanceof GramJs.Photo ? buildApiPhoto(personalPhoto) : undefined, personalPhoto: personalPhoto instanceof GramJs.Photo ? buildApiPhoto(personalPhoto) : undefined,
botInfo: botInfo && buildApiBotInfo(botInfo, userId), botInfo: botInfo && buildApiBotInfo(botInfo, userId),
isContactRequirePremium: contactRequirePremium, isContactRequirePremium: contactRequirePremium,
shouldDisplayGiftsButton: displayGiftsButton,
disallowedGifts: disallowedGifts && buildApiDisallowedGiftsSettings(disallowedGifts),
birthday: birthday && buildApiBirthday(birthday), birthday: birthday && buildApiBirthday(birthday),
businessLocation: businessLocation && buildApiBusinessLocation(businessLocation), businessLocation: businessLocation && buildApiBusinessLocation(businessLocation),
businessWorkHours: businessWorkHours && buildApiBusinessWorkHours(businessWorkHours), businessWorkHours: businessWorkHours && buildApiBusinessWorkHours(businessWorkHours),

View File

@ -8,6 +8,7 @@ import type {
ApiChatBannedRights, ApiChatBannedRights,
ApiChatFolder, ApiChatFolder,
ApiChatReactions, ApiChatReactions,
ApiDisallowedGiftsSettings,
ApiEmojiStatusType, ApiEmojiStatusType,
ApiFormattedText, ApiFormattedText,
ApiGroupCall, 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) { export function buildInputInvoice(invoice: ApiRequestInputInvoice) {
switch (invoice.type) { switch (invoice.type) {
case 'message': { case 'message': {

View File

@ -1,5 +1,6 @@
import BigInt from 'big-integer'; import BigInt from 'big-integer';
import { Api as GramJs } from '../../../lib/gramjs'; import { Api as GramJs } from '../../../lib/gramjs';
import { RPCError } from '../../../lib/gramjs/errors';
import type { import type {
ApiChat, ApiChat,
@ -184,21 +185,14 @@ export async function getPaymentForm(inputInvoice: ApiRequestInputInvoice, theme
} }
return buildApiPaymentForm(result); return buildApiPaymentForm(result);
} catch (err) { } catch (err: any) {
if (err instanceof Error) { if (err instanceof RPCError) {
// Can be removed if separate error handling is added to payment UI
sendApiUpdate({
'@type': 'error',
error: {
message: err.message,
hasErrorKey: true,
},
});
return { return {
error: err.message, error: err.errorMessage,
}; };
} else {
throw err;
} }
return undefined;
} }
} }

View File

@ -6,6 +6,7 @@ import type { LANG_PACKS } from '../../../config';
import type { import type {
ApiAppConfig, ApiAppConfig,
ApiConfig, ApiConfig,
ApiDisallowedGiftsSettings,
ApiInputPrivacyRules, ApiInputPrivacyRules,
ApiLanguage, ApiLanguage,
ApiNotifyPeerType, ApiNotifyPeerType,
@ -24,6 +25,7 @@ import {
import { buildCollectionByKey } from '../../../util/iteratees'; import { buildCollectionByKey } from '../../../util/iteratees';
import { buildAppConfig } from '../apiBuilders/appConfig'; import { buildAppConfig } from '../apiBuilders/appConfig';
import { buildApiPhoto, buildPrivacyRules } from '../apiBuilders/common'; import { buildApiPhoto, buildPrivacyRules } from '../apiBuilders/common';
import { buildApiDisallowedGiftsSettings } from '../apiBuilders/gifts';
import { import {
buildApiConfig, buildApiConfig,
buildApiCountryList, buildApiCountryList,
@ -39,6 +41,7 @@ import {
} from '../apiBuilders/misc'; } from '../apiBuilders/misc';
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers'; import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
import { import {
buildDisallowedGiftsSettings,
buildInputEntity, buildInputPeer, buildInputPhoto, buildInputEntity, buildInputPeer, buildInputPhoto,
buildInputPrivacyKey, buildInputPrivacyKey,
buildInputPrivacyRules, buildInputPrivacyRules,
@ -657,6 +660,8 @@ export async function fetchGlobalPrivacySettings() {
shouldHideReadMarks: Boolean(result.hideReadMarks), shouldHideReadMarks: Boolean(result.hideReadMarks),
shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium), shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium),
nonContactPeersPaidStars: Number(result.noncontactPeersPaidStars), nonContactPeersPaidStars: Number(result.noncontactPeersPaidStars),
shouldDisplayGiftsButton: Boolean(result.displayGiftsButton),
disallowedGifts: result.disallowedGifts && buildApiDisallowedGiftsSettings(result.disallowedGifts),
}; };
} }
@ -665,18 +670,24 @@ export async function updateGlobalPrivacySettings({
shouldHideReadMarks, shouldHideReadMarks,
shouldNewNonContactPeersRequirePremium, shouldNewNonContactPeersRequirePremium,
nonContactPeersPaidStars, nonContactPeersPaidStars,
shouldDisplayGiftsButton,
disallowedGifts,
}: { }: {
shouldArchiveAndMuteNewNonContact?: boolean; shouldArchiveAndMuteNewNonContact?: boolean;
shouldHideReadMarks?: boolean; shouldHideReadMarks?: boolean;
shouldNewNonContactPeersRequirePremium?: boolean; shouldNewNonContactPeersRequirePremium?: boolean;
nonContactPeersPaidStars?: number | null; nonContactPeersPaidStars?: number | null;
shouldDisplayGiftsButton?: boolean;
disallowedGifts?: ApiDisallowedGiftsSettings;
}) { }) {
const result = await invokeRequest(new GramJs.account.SetGlobalPrivacySettings({ const result = await invokeRequest(new GramJs.account.SetGlobalPrivacySettings({
settings: new GramJs.GlobalPrivacySettings({ settings: new GramJs.GlobalPrivacySettings({
...(shouldArchiveAndMuteNewNonContact && { archiveAndMuteNewNoncontactPeers: true }), ...(shouldArchiveAndMuteNewNonContact && { archiveAndMuteNewNoncontactPeers: true }),
...(shouldHideReadMarks && { hideReadMarks: true }), ...(shouldHideReadMarks && { hideReadMarks: true }),
...(shouldNewNonContactPeersRequirePremium && { newNoncontactPeersRequirePremium: true }), ...(shouldNewNonContactPeersRequirePremium && { newNoncontactPeersRequirePremium: true }),
displayGiftsButton: shouldDisplayGiftsButton || undefined,
noncontactPeersPaidStars: BigInt(nonContactPeersPaidStars || 0), noncontactPeersPaidStars: BigInt(nonContactPeersPaidStars || 0),
disallowedGifts: disallowedGifts && buildDisallowedGiftsSettings(disallowedGifts),
}), }),
})); }));
@ -689,6 +700,8 @@ export async function updateGlobalPrivacySettings({
shouldHideReadMarks: Boolean(result.hideReadMarks), shouldHideReadMarks: Boolean(result.hideReadMarks),
shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium), shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium),
nonContactPeersPaidStars: Number(result.noncontactPeersPaidStars), nonContactPeersPaidStars: Number(result.noncontactPeersPaidStars),
shouldDisplayGiftsButton,
disallowedGifts,
}; };
} }

View File

@ -215,3 +215,10 @@ export interface ApiStarsGiveawayWinnerOption {
users: number; users: number;
perUserStars: number; perUserStars: number;
} }
export interface ApiDisallowedGiftsSettings {
shouldDisallowUnlimitedStarGifts?: true;
shouldDisallowLimitedStarGifts?: true;
shouldDisallowUniqueStarGifts?: true;
shouldDisallowPremiumGifts?: true;
}

View File

@ -55,6 +55,8 @@ export interface ApiUserFullInfo {
areAdsEnabled?: boolean; areAdsEnabled?: boolean;
hasPinnedStories?: boolean; hasPinnedStories?: boolean;
isContactRequirePremium?: boolean; isContactRequirePremium?: boolean;
shouldDisplayGiftsButton?: boolean;
disallowedGifts?: ApiDisallowedGifts;
birthday?: ApiBirthday; birthday?: ApiBirthday;
personalChannelId?: string; personalChannelId?: string;
personalChannelMessageId?: number; personalChannelMessageId?: number;
@ -156,3 +158,10 @@ export interface ApiBirthday {
month: number; month: number;
year?: number; year?: number;
} }
export interface ApiDisallowedGifts {
shouldDisallowUnlimitedStarGifts?: boolean;
shouldDisallowLimitedStarGifts?: boolean;
shouldDisallowUniqueStarGifts?: boolean;
shouldDisallowPremiumGifts?: boolean;
}

View File

@ -1564,7 +1564,28 @@
"PrivacyGifts" = "Gifts"; "PrivacyGifts" = "Gifts";
"PrivacyGiftsTitle" = "Who can display gifts on my profile?"; "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."; "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"; "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."; "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_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?"; "StarsSubscribeBotText_other" = "Do you want to subscribe to **{name}** in **{bot}** for **{amount}** stars per month?";

View File

@ -15,6 +15,7 @@ import type {
ApiBotMenuButton, ApiBotMenuButton,
ApiChat, ApiChat,
ApiChatFullInfo, ApiChatFullInfo,
ApiDisallowedGifts,
ApiDraft, ApiDraft,
ApiFormattedText, ApiFormattedText,
ApiMessage, ApiMessage,
@ -227,6 +228,7 @@ type StateProps =
isRightColumnShown?: boolean; isRightColumnShown?: boolean;
isSelectModeActive?: boolean; isSelectModeActive?: boolean;
isReactionPickerOpen?: boolean; isReactionPickerOpen?: boolean;
shouldDisplayGiftsButton?: boolean;
isForwarding?: boolean; isForwarding?: boolean;
forwardedMessagesCount?: number; forwardedMessagesCount?: number;
pollModal: TabState['pollModal']; pollModal: TabState['pollModal'];
@ -292,6 +294,7 @@ type StateProps =
isPaymentMessageConfirmDialogOpen: boolean; isPaymentMessageConfirmDialogOpen: boolean;
starsBalance: number; starsBalance: number;
isStarsBalanceModalOpen: boolean; isStarsBalanceModalOpen: boolean;
disallowedGifts?: ApiDisallowedGifts;
isAccountFrozen?: boolean; isAccountFrozen?: boolean;
isAppConfigLoaded?: boolean; isAppConfigLoaded?: boolean;
}; };
@ -346,6 +349,7 @@ const Composer: FC<OwnProps & StateProps> = ({
isRightColumnShown, isRightColumnShown,
isSelectModeActive, isSelectModeActive,
isReactionPickerOpen, isReactionPickerOpen,
shouldDisplayGiftsButton,
isForwarding, isForwarding,
forwardedMessagesCount, forwardedMessagesCount,
pollModal, pollModal,
@ -414,6 +418,7 @@ const Composer: FC<OwnProps & StateProps> = ({
isPaymentMessageConfirmDialogOpen, isPaymentMessageConfirmDialogOpen,
starsBalance, starsBalance,
isStarsBalanceModalOpen, isStarsBalanceModalOpen,
disallowedGifts,
isAccountFrozen, isAccountFrozen,
isAppConfigLoaded, isAppConfigLoaded,
}) => { }) => {
@ -434,6 +439,7 @@ const Composer: FC<OwnProps & StateProps> = ({
showNotification, showNotification,
showAllowedMessageTypesNotification, showAllowedMessageTypesNotification,
openStoryReactionPicker, openStoryReactionPicker,
openGiftModal,
closeReactionPicker, closeReactionPicker,
sendStoryReaction, sendStoryReaction,
editMessage, editMessage,
@ -835,6 +841,15 @@ const Composer: FC<OwnProps & StateProps> = ({
}; };
}, [chatId, threadId, resetComposerRef, stopRecordingVoiceRef]); }, [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 showCustomEmojiPremiumNotification = useLastCallback(() => {
const notificationNumber = customEmojiNotificationNumber.current; const notificationNumber = customEmojiNotificationNumber.current;
if (!notificationNumber) { if (!notificationNumber) {
@ -1488,6 +1503,10 @@ const Composer: FC<OwnProps & StateProps> = ({
}); });
}); });
const handleGiftClick = useLastCallback(() => {
openGiftModal({ forUserId: chatId });
});
const handleToggleSilentPosting = useLastCallback(() => { const handleToggleSilentPosting = useLastCallback(() => {
const newValue = !isSilentPosting; const newValue = !isSilentPosting;
updateChatSilentPosting({ chatId, isEnabled: newValue }); updateChatSilentPosting({ chatId, isEnabled: newValue });
@ -2061,6 +2080,17 @@ const Composer: FC<OwnProps & StateProps> = ({
<Icon name="schedule" /> <Icon name="schedule" />
</Button> </Button>
)} )}
{shouldShowGiftButton && (
<Button
round
faded
className="composer-action-button"
color="translucent"
onClick={handleGiftClick}
>
<Icon name="gift" />
</Button>
)}
{Boolean(botKeyboardMessageId) && !activeVoiceRecording && !editingMessage && ( {Boolean(botKeyboardMessageId) && !activeVoiceRecording && !editingMessage && (
<ResponsiveHoverButton <ResponsiveHoverButton
className={buildClassName('composer-action-button', isBotKeyboardOpen && 'activated')} className={buildClassName('composer-action-button', isBotKeyboardOpen && 'activated')}
@ -2464,6 +2494,8 @@ export default memo(withGlobal<OwnProps>(
isPaymentMessageConfirmDialogOpen: tabState.isPaymentMessageConfirmDialogOpen, isPaymentMessageConfirmDialogOpen: tabState.isPaymentMessageConfirmDialogOpen,
starsBalance, starsBalance,
isStarsBalanceModalOpen, isStarsBalanceModalOpen,
shouldDisplayGiftsButton: userFullInfo?.shouldDisplayGiftsButton,
disallowedGifts: userFullInfo?.disallowedGifts,
isAccountFrozen, isAccountFrozen,
isAppConfigLoaded, isAppConfigLoaded,
}; };

View File

@ -188,6 +188,10 @@
&[dir="rtl"] { &[dir="rtl"] {
text-align: right; text-align: right;
} }
.gift-icon {
vertical-align: text-top;
}
} }
.ListItem { .ListItem {

View File

@ -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 (
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('PrivacyAcceptedGiftTitle')}
</h4>
<ListItem onClick={handleLimitedEditionChange}>
<span>{lang('PrivacyGiftLimitedEdition')}</span>
<Switcher
id="limited_edition"
label={disallowedGifts?.shouldDisallowLimitedStarGifts ? lang('PrivacyDisableLimitedEditionStarGifts')
: lang('PrivacyEnableLimitedEditionStarGifts')}
disabled={!isCurrentUserPremium}
checked={!isCurrentUserPremium ? true : !disallowedGifts?.shouldDisallowLimitedStarGifts}
/>
</ListItem>
<ListItem onClick={handleUnlimitedEditionChange}>
<span>{lang('PrivacyGiftUnlimited')}</span>
<Switcher
id="unlimited"
label={disallowedGifts?.shouldDisallowUnlimitedStarGifts ? lang('PrivacyDisableUnlimitedStarGifts')
: lang('PrivacyEnableUnlimitedStarGifts')}
disabled={!isCurrentUserPremium}
checked={!isCurrentUserPremium ? true : !disallowedGifts?.shouldDisallowUnlimitedStarGifts}
/>
</ListItem>
<ListItem onClick={handleUniqueChange}>
<span>{lang('PrivacyGiftUnique')}</span>
<Switcher
id="unique"
label={disallowedGifts?.shouldDisallowUniqueStarGifts ? lang('PrivacyDisableUniqueStarGifts')
: lang('PrivacyEnableUniqueStarGifts')}
disabled={!isCurrentUserPremium}
checked={!isCurrentUserPremium ? true : !disallowedGifts?.shouldDisallowUniqueStarGifts}
/>
</ListItem>
<ListItem onClick={handlePremiumSubscriptionChange}>
<span>{lang('PrivacyGiftPremiumSubscription')}</span>
<Switcher
id="premium_subscription"
label={disallowedGifts?.shouldDisallowPremiumGifts ? lang('PrivacyDisablePremiumGifts')
: lang('PrivacyEnablePremiumGifts')}
disabled={!isCurrentUserPremium}
checked={!isCurrentUserPremium ? true : !disallowedGifts?.shouldDisallowPremiumGifts}
/>
</ListItem>
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('PrivacyAcceptedGiftInfo')}
</p>
</div>
);
};
export default memo(withGlobal(
(global): StateProps => {
const {
settings: {
byKey: {
disallowedGifts,
},
},
} = global;
return {
disallowedGifts,
isCurrentUserPremium: selectIsCurrentUserPremium(global),
};
},
)(SettingsAcceptedGift));

View File

@ -13,10 +13,13 @@ import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import Icon from '../../common/icons/Icon';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import RadioGroup from '../../ui/RadioGroup'; import RadioGroup from '../../ui/RadioGroup';
import Switcher from '../../ui/Switcher';
import PremiumStatusItem from './PremiumStatusItem'; import PremiumStatusItem from './PremiumStatusItem';
import PrivacyLockedOption from './PrivacyLockedOption'; import PrivacyLockedOption from './PrivacyLockedOption';
import SettingsAcceptedGift from './SettingsAcceptedGift';
import SettingsPrivacyLastSeen from './SettingsPrivacyLastSeen'; import SettingsPrivacyLastSeen from './SettingsPrivacyLastSeen';
import SettingsPrivacyPublicProfilePhoto from './SettingsPrivacyPublicProfilePhoto'; import SettingsPrivacyPublicProfilePhoto from './SettingsPrivacyPublicProfilePhoto';
@ -34,6 +37,8 @@ type StateProps = {
primaryPrivacy?: ApiPrivacySettings; primaryPrivacy?: ApiPrivacySettings;
secondaryPrivacy?: ApiPrivacySettings; secondaryPrivacy?: ApiPrivacySettings;
isPremiumRequired?: boolean; isPremiumRequired?: boolean;
shouldDisplayGiftsButton?: boolean;
isCurrentUserPremium?: boolean;
}; };
const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
@ -47,12 +52,36 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
isPremiumRequired, isPremiumRequired,
onScreenSelect, onScreenSelect,
onReset, onReset,
shouldDisplayGiftsButton,
isCurrentUserPremium,
}) => { }) => {
const lang = useLang();
const { updateGlobalPrivacySettings, showNotification } = getActions();
useHistoryBack({ useHistoryBack({
isActive, isActive,
onBack: onReset, 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(() => { const secondaryScreen = useMemo(() => {
switch (screen) { switch (screen) {
case SettingsScreens.PrivacyPhoneCall: case SettingsScreens.PrivacyPhoneCall:
@ -67,6 +96,27 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
{screen === SettingsScreens.PrivacyGifts && (
<div className="settings-item">
<ListItem onClick={handleShowGiftIconInChats}>
<span>{lang('PrivacyDisplayGiftsButton')}</span>
<Switcher
id="gift"
disabled={!isCurrentUserPremium}
label={shouldDisplayGiftsButton ? lang('HideGiftsButton') : lang('DisplayGiftsButton')}
checked={shouldDisplayGiftsButton}
/>
</ListItem>
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('PrivacyDisplayGiftIconInChats', {
icon: <Icon name="gift" className="gift-icon" />,
gift: lang('PrivacyDisplayGift'),
}, {
withNodes: true,
})}
</p>
</div>
)}
<PrivacySubsection <PrivacySubsection
screen={screen} screen={screen}
privacy={primaryPrivacy} privacy={primaryPrivacy}
@ -83,6 +133,9 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
{screen === SettingsScreens.PrivacyLastSeen && ( {screen === SettingsScreens.PrivacyLastSeen && (
<SettingsPrivacyLastSeen visibility={primaryPrivacy?.visibility} /> <SettingsPrivacyLastSeen visibility={primaryPrivacy?.visibility} />
)} )}
{screen === SettingsScreens.PrivacyGifts && (
<SettingsAcceptedGift />
)}
{secondaryScreen && ( {secondaryScreen && (
<PrivacySubsection <PrivacySubsection
screen={secondaryScreen} screen={secondaryScreen}
@ -361,7 +414,12 @@ export default memo(withGlobal<OwnProps>(
const { const {
currentUserId, currentUserId,
settings: { privacy }, settings: {
privacy,
byKey: {
shouldDisplayGiftsButton,
},
},
} = global; } = global;
const currentUserFullInfo = selectUserFullInfo(global, currentUserId!); const currentUserFullInfo = selectUserFullInfo(global, currentUserId!);
@ -426,6 +484,8 @@ export default memo(withGlobal<OwnProps>(
hasCurrentUserFullInfo: Boolean(currentUserFullInfo), hasCurrentUserFullInfo: Boolean(currentUserFullInfo),
currentUserFallbackPhoto: currentUserFullInfo?.fallbackPhoto, currentUserFallbackPhoto: currentUserFullInfo?.fallbackPhoto,
isPremiumRequired: screen === SettingsScreens.PrivacyVoiceMessages && !selectIsCurrentUserPremium(global), isPremiumRequired: screen === SettingsScreens.PrivacyVoiceMessages && !selectIsCurrentUserPremium(global),
shouldDisplayGiftsButton,
isCurrentUserPremium: selectIsCurrentUserPremium(global),
}; };
}, },
)(SettingsPrivacyVisibility)); )(SettingsPrivacyVisibility));

View File

@ -4,7 +4,9 @@ import React, {
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; 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 { IAnchorPosition, ThreadId } from '../../types';
import type { IconName } from '../../types/icons'; import type { IconName } from '../../types/icons';
import { MAIN_THREAD_ID } from '../../api/types'; import { MAIN_THREAD_ID } from '../../api/types';
@ -22,6 +24,7 @@ import {
isUserRightBanned, isUserRightBanned,
} from '../../global/helpers'; } from '../../global/helpers';
import { getIsChatMuted } from '../../global/helpers/notifications'; import { getIsChatMuted } from '../../global/helpers/notifications';
import { getPeerFullTitle } from '../../global/helpers/peers';
import { import {
selectBot, selectBot,
selectCanGift, selectCanGift,
@ -44,6 +47,7 @@ import { disableScrolling } from '../../util/scrollLock';
import useAppLayout from '../../hooks/useAppLayout'; import useAppLayout from '../../hooks/useAppLayout';
import useFlag from '../../hooks/useFlag'; import useFlag from '../../hooks/useFlag';
import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback'; import useLastCallback from '../../hooks/useLastCallback';
import useOldLang from '../../hooks/useOldLang'; import useOldLang from '../../hooks/useOldLang';
import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation'; import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
@ -123,6 +127,7 @@ type StateProps = {
isBot?: boolean; isBot?: boolean;
isChatWithSelf?: boolean; isChatWithSelf?: boolean;
savedDialog?: ApiChat; savedDialog?: ApiChat;
disallowedGifts?: ApiDisallowedGifts;
isAccountFrozen?: boolean; isAccountFrozen?: boolean;
}; };
@ -178,6 +183,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
onAsMessagesClick, onAsMessagesClick,
onClose, onClose,
onCloseAnimationEnd, onCloseAnimationEnd,
disallowedGifts,
isAccountFrozen, isAccountFrozen,
}) => { }) => {
const { const {
@ -207,8 +213,12 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
setViewForumAsMessages, setViewForumAsMessages,
openBoostModal, openBoostModal,
reportMessages, reportMessages,
showNotification,
} = getActions(); } = getActions();
const oldLang = useOldLang();
const lang = useLang();
const { isMobile } = useAppLayout(); const { isMobile } = useAppLayout();
const [isMenuOpen, setIsMenuOpen] = useState(true); const [isMenuOpen, setIsMenuOpen] = useState(true);
const [shouldCloseFast, setShouldCloseFast] = useState(false); const [shouldCloseFast, setShouldCloseFast] = useState(false);
@ -222,6 +232,13 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
(!isChatInfoShown && isForum) ? true : undefined, CLOSE_MENU_ANIMATION_DURATION, (!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(() => { const closeMuteModal = useLastCallback(() => {
setIsMuteModalOpen(false); setIsMuteModalOpen(false);
onClose(); onClose();
@ -357,6 +374,11 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
}); });
const handleGiftClick = useLastCallback(() => { const handleGiftClick = useLastCallback(() => {
if (areAllGiftsDisallowed && chat) {
showNotification({ message: lang('SendDisallowError') });
return;
}
openGiftModal({ forUserId: chatId });
if (isAccountFrozen) { if (isAccountFrozen) {
openFrozenAccountModal(); openFrozenAccountModal();
} else { } else {
@ -469,8 +491,6 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
useEffect(disableScrolling, []); useEffect(disableScrolling, []);
const lang = useOldLang();
const botButtons = useMemo(() => { const botButtons = useMemo(() => {
const commandButtons = botCommands?.map(({ command }) => { const commandButtons = botCommands?.map(({ command }) => {
const cmd = BOT_BUTTONS[command]; const cmd = BOT_BUTTONS[command];
@ -488,7 +508,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={handleClick} onClick={handleClick}
> >
{lang(cmd.label)} {oldLang(cmd.label)}
</MenuItem> </MenuItem>
); );
}); });
@ -503,39 +523,39 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
if (hasPrivacyCommand && !botPrivacyPolicyUrl) { if (hasPrivacyCommand && !botPrivacyPolicyUrl) {
sendBotCommand({ command: '/privacy' }); sendBotCommand({ command: '/privacy' });
} else { } else {
openUrl({ url: botPrivacyPolicyUrl || lang('BotDefaultPrivacyPolicy') }); openUrl({ url: botPrivacyPolicyUrl || oldLang('BotDefaultPrivacyPolicy') });
} }
closeMenu(); closeMenu();
}} }}
> >
{lang('BotPrivacyPolicy')} {oldLang('BotPrivacyPolicy')}
</MenuItem> </MenuItem>
); );
return [...commandButtons || [], privacyButton].filter(Boolean); return [...commandButtons || [], privacyButton].filter(Boolean);
}, [botCommands, lang, botPrivacyPolicyUrl, isBot]); }, [botCommands, oldLang, botPrivacyPolicyUrl, isBot]);
const deleteTitle = useMemo(() => { const deleteTitle = useMemo(() => {
if (!chat) return undefined; if (!chat) return undefined;
if (savedDialog) { if (savedDialog) {
return lang('Delete'); return oldLang('Delete');
} }
if (isPrivate) { if (isPrivate) {
return lang('DeleteChatUser'); return oldLang('DeleteChatUser');
} }
if (canDeleteChat) { if (canDeleteChat) {
return lang('GroupInfo.DeleteAndExit'); return oldLang('GroupInfo.DeleteAndExit');
} }
if (isChannel) { if (isChannel) {
return lang('LeaveChannel'); return oldLang('LeaveChannel');
} }
return lang('Group.LeaveGroup'); return oldLang('Group.LeaveGroup');
}, [canDeleteChat, chat, isChannel, isPrivate, savedDialog, lang]); }, [canDeleteChat, chat, isChannel, isPrivate, savedDialog, oldLang]);
return ( return (
<Portal> <Portal>
@ -552,7 +572,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="search" icon="search"
onClick={handleSearch} onClick={handleSearch}
> >
{lang('Search')} {oldLang('Search')}
</MenuItem> </MenuItem>
)} )}
{withForumActions && canCreateTopic && ( {withForumActions && canCreateTopic && (
@ -561,7 +581,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="comments" icon="comments"
onClick={handleCreateTopicClick} onClick={handleCreateTopicClick}
> >
{lang('lng_forum_create_topic')} {oldLang('lng_forum_create_topic')}
</MenuItem> </MenuItem>
<MenuSeparator /> <MenuSeparator />
</> </>
@ -571,7 +591,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="info" icon="info"
onClick={handleViewGroupInfo} onClick={handleViewGroupInfo}
> >
{isTopic ? lang('lng_context_view_topic') : lang('lng_context_view_group')} {isTopic ? oldLang('lng_context_view_topic') : oldLang('lng_context_view_group')}
</MenuItem> </MenuItem>
)} )}
{canManage && !canEditTopic && ( {canManage && !canEditTopic && (
@ -579,7 +599,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="edit" icon="edit"
onClick={handleEditClick} onClick={handleEditClick}
> >
{lang('Edit')} {oldLang('Edit')}
</MenuItem> </MenuItem>
)} )}
{canEditTopic && ( {canEditTopic && (
@ -587,7 +607,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="edit" icon="edit"
onClick={handleEditTopicClick} onClick={handleEditTopicClick}
> >
{lang('lng_forum_topic_edit')} {oldLang('lng_forum_topic_edit')}
</MenuItem> </MenuItem>
)} )}
{isMobile && !withForumActions && isForum && !isTopic && ( {isMobile && !withForumActions && isForum && !isTopic && (
@ -595,7 +615,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="forums" icon="forums"
onClick={handleViewAsTopicsClick} onClick={handleViewAsTopicsClick}
> >
{lang('Chat.ContextViewAsTopics')} {oldLang('Chat.ContextViewAsTopics')}
</MenuItem> </MenuItem>
)} )}
{withForumActions && Boolean(pendingJoinRequests) && ( {withForumActions && Boolean(pendingJoinRequests) && (
@ -603,7 +623,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="user" icon="user"
onClick={onJoinRequestsClick} onClick={onJoinRequestsClick}
> >
{isChannel ? lang('SubscribeRequests') : lang('MemberRequests')} {isChannel ? oldLang('SubscribeRequests') : oldLang('MemberRequests')}
<div className="right-badge">{pendingJoinRequests}</div> <div className="right-badge">{pendingJoinRequests}</div>
</MenuItem> </MenuItem>
)} )}
@ -612,7 +632,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="message" icon="message"
onClick={handleOpenAsMessages} onClick={handleOpenAsMessages}
> >
{lang('lng_forum_view_as_messages')} {oldLang('lng_forum_view_as_messages')}
</MenuItem> </MenuItem>
)} )}
{withExtraActions && canStartBot && ( {withExtraActions && canStartBot && (
@ -620,7 +640,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="bots" icon="bots"
onClick={handleStartBot} onClick={handleStartBot}
> >
{lang('BotStart')} {oldLang('BotStart')}
</MenuItem> </MenuItem>
)} )}
{withExtraActions && canSubscribe && ( {withExtraActions && canSubscribe && (
@ -628,7 +648,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon={isChannel ? 'channel' : 'group'} icon={isChannel ? 'channel' : 'group'}
onClick={handleSubscribe} onClick={handleSubscribe}
> >
{lang(isChannel ? 'ProfileJoinChannel' : 'ProfileJoinGroup')} {oldLang(isChannel ? 'ProfileJoinChannel' : 'ProfileJoinGroup')}
</MenuItem> </MenuItem>
)} )}
{canShowBoostModal && !canViewBoosts && ( {canShowBoostModal && !canViewBoosts && (
@ -636,7 +656,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="boost-outline" icon="boost-outline"
onClick={handleBoostClick} onClick={handleBoostClick}
> >
{lang(isChannel ? 'BoostingBoostChannelMenu' : 'BoostingBoostGroupMenu')} {oldLang(isChannel ? 'BoostingBoostChannelMenu' : 'BoostingBoostGroupMenu')}
</MenuItem> </MenuItem>
)} )}
{canAddContact && ( {canAddContact && (
@ -644,7 +664,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="add-user" icon="add-user"
onClick={handleAddContactClick} onClick={handleAddContactClick}
> >
{lang('AddContact')} {oldLang('AddContact')}
</MenuItem> </MenuItem>
)} )}
{isMobile && canCall && ( {isMobile && canCall && (
@ -652,7 +672,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="phone" icon="phone"
onClick={handleCall} onClick={handleCall}
> >
{lang('Call')} {oldLang('Call')}
</MenuItem> </MenuItem>
)} )}
{canCall && ( {canCall && (
@ -660,7 +680,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="video-outlined" icon="video-outlined"
onClick={handleVideoCall} onClick={handleVideoCall}
> >
{lang('VideoCall')} {oldLang('VideoCall')}
</MenuItem> </MenuItem>
)} )}
{canMute && (isMuted ? ( {canMute && (isMuted ? (
@ -668,7 +688,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="unmute" icon="unmute"
onClick={handleUnmuteClick} onClick={handleUnmuteClick}
> >
{lang('ChatsUnmute')} {oldLang('ChatsUnmute')}
</MenuItem> </MenuItem>
) )
: ( : (
@ -676,7 +696,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="mute" icon="mute"
onClick={handleMuteClick} onClick={handleMuteClick}
> >
{lang('ChatsMute')}... {oldLang('ChatsMute')}...
</MenuItem> </MenuItem>
) )
)} )}
@ -685,7 +705,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="voice-chat" icon="voice-chat"
onClick={handleEnterVoiceChatClick} onClick={handleEnterVoiceChatClick}
> >
{lang(canCreateVoiceChat ? 'StartVoipChat' : 'VoipGroupJoinCall')} {oldLang(canCreateVoiceChat ? 'StartVoipChat' : 'VoipGroupJoinCall')}
</MenuItem> </MenuItem>
)} )}
{hasLinkedChat && ( {hasLinkedChat && (
@ -693,7 +713,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon={isChannel ? 'comments' : 'channel'} icon={isChannel ? 'comments' : 'channel'}
onClick={handleLinkedChatClick} onClick={handleLinkedChatClick}
> >
{lang(isChannel ? 'ViewDiscussion' : 'lng_profile_view_channel')} {oldLang(isChannel ? 'ViewDiscussion' : 'lng_profile_view_channel')}
</MenuItem> </MenuItem>
)} )}
{!withForumActions && ( {!withForumActions && (
@ -701,7 +721,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="select" icon="select"
onClick={handleSelectMessages} onClick={handleSelectMessages}
> >
{lang('ReportSelectMessages')} {oldLang('ReportSelectMessages')}
</MenuItem> </MenuItem>
)} )}
{canViewBoosts && ( {canViewBoosts && (
@ -709,7 +729,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="boost-outline" icon="boost-outline"
onClick={handleBoostClick} onClick={handleBoostClick}
> >
{lang('Boosts')} {oldLang('Boosts')}
</MenuItem> </MenuItem>
)} )}
{canViewStatistics && ( {canViewStatistics && (
@ -717,7 +737,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="stats" icon="stats"
onClick={handleStatisticsClick} onClick={handleStatisticsClick}
> >
{lang('Statistics')} {oldLang('Statistics')}
</MenuItem> </MenuItem>
)} )}
{isChannel && canViewMonetization && ( {isChannel && canViewMonetization && (
@ -725,7 +745,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="cash-circle" icon="cash-circle"
onClick={handleMonetizationClick} onClick={handleMonetizationClick}
> >
{lang('lng_channel_earn_title')} {oldLang('lng_channel_earn_title')}
</MenuItem> </MenuItem>
)} )}
{canTranslate && ( {canTranslate && (
@ -733,7 +753,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="language" icon="language"
onClick={handleEnableTranslations} onClick={handleEnableTranslations}
> >
{lang('lng_context_translate')} {oldLang('lng_context_translate')}
</MenuItem> </MenuItem>
)} )}
{canReportChat && ( {canReportChat && (
@ -741,7 +761,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="flag" icon="flag"
onClick={handleReport} onClick={handleReport}
> >
{lang('ReportPeer.Report')} {oldLang('ReportPeer.Report')}
</MenuItem> </MenuItem>
)} )}
{botButtons} {botButtons}
@ -750,7 +770,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="gift" icon="gift"
onClick={handleGiftClick} onClick={handleGiftClick}
> >
{lang('ProfileSendAGift')} {oldLang('ProfileSendAGift')}
</MenuItem> </MenuItem>
)} )}
{isBot && ( {isBot && (
@ -758,7 +778,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon={isBlocked ? 'bots' : 'hand-stop'} icon={isBlocked ? 'bots' : 'hand-stop'}
onClick={isBlocked ? handleRestartBot : handleBlock} onClick={isBlocked ? handleRestartBot : handleBlock}
> >
{isBlocked ? lang('BotRestart') : lang('Bot.Stop')} {isBlocked ? oldLang('BotRestart') : oldLang('Bot.Stop')}
</MenuItem> </MenuItem>
)} )}
{isPrivate && !isChatWithSelf && !isBot && ( {isPrivate && !isChatWithSelf && !isBot && (
@ -766,7 +786,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon={isBlocked ? 'user' : 'hand-stop'} icon={isBlocked ? 'user' : 'hand-stop'}
onClick={isBlocked ? handleUnblock : handleBlock} onClick={isBlocked ? handleUnblock : handleBlock}
> >
{isBlocked ? lang('Unblock') : lang('BlockUser')} {isBlocked ? oldLang('Unblock') : oldLang('BlockUser')}
</MenuItem> </MenuItem>
)} )}
{canLeave && ( {canLeave && (
@ -861,6 +881,7 @@ export default memo(withGlobal<OwnProps>(
isBot: Boolean(chatBot), isBot: Boolean(chatBot),
isChatWithSelf, isChatWithSelf,
savedDialog, savedDialog,
disallowedGifts: userFullInfo?.disallowedGifts,
isAccountFrozen, isAccountFrozen,
}; };
}, },

View File

@ -1,6 +1,6 @@
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import React, { import React, {
memo, useMemo, useState, memo, useEffect, useMemo, useState,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
@ -10,11 +10,9 @@ import {
type ApiMessage, type ApiPeer, type ApiStarsAmount, MAIN_THREAD_ID, type ApiMessage, type ApiPeer, type ApiStarsAmount, MAIN_THREAD_ID,
} from '../../../api/types'; } from '../../../api/types';
import {
} from '../../../global/helpers';
import { getPeerTitle, isApiPeerUser } from '../../../global/helpers/peers'; import { getPeerTitle, isApiPeerUser } from '../../../global/helpers/peers';
import { import {
selectPeer, selectPeerPaidMessagesStars, selectTabState, selectTheme, selectThemeValues, selectPeer, selectPeerPaidMessagesStars, selectTabState, selectTheme, selectThemeValues, selectUserFullInfo,
} from '../../../global/selectors'; } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import buildStyle from '../../../util/buildStyle'; import buildStyle from '../../../util/buildStyle';
@ -53,6 +51,8 @@ export type StateProps = {
isPaymentFormLoading?: boolean; isPaymentFormLoading?: boolean;
starBalance?: ApiStarsAmount; starBalance?: ApiStarsAmount;
paidMessagesStars?: number; paidMessagesStars?: number;
areUniqueStarGiftsDisallowed?: boolean;
shouldDisallowLimitedStarGifts?: boolean;
}; };
const LIMIT_DISPLAY_THRESHOLD = 50; const LIMIT_DISPLAY_THRESHOLD = 50;
@ -72,6 +72,8 @@ function GiftComposer({
isPaymentFormLoading, isPaymentFormLoading,
starBalance, starBalance,
paidMessagesStars, paidMessagesStars,
areUniqueStarGiftsDisallowed,
shouldDisallowLimitedStarGifts,
}: OwnProps & StateProps) { }: OwnProps & StateProps) {
const { const {
sendStarGift, sendPremiumGiftByStars, openInvoice, openGiftUpgradeModal, openStarsBalanceModal, sendStarGift, sendPremiumGiftByStars, openInvoice, openGiftUpgradeModal, openStarsBalanceModal,
@ -86,6 +88,12 @@ function GiftComposer({
const customBackgroundValue = useCustomBackground(theme, customBackground); const customBackgroundValue = useCustomBackground(theme, customBackground);
useEffect(() => {
if (shouldDisallowLimitedStarGifts) {
setShouldPayForUpgrade(true);
}
}, [shouldDisallowLimitedStarGifts, shouldPayForUpgrade]);
const isStarGift = 'id' in gift; const isStarGift = 'id' in gift;
const hasPremiumByStars = giftByStars && 'amount' in giftByStars; const hasPremiumByStars = giftByStars && 'amount' in giftByStars;
const isPeerUser = peer && isApiPeerUser(peer); const isPeerUser = peer && isApiPeerUser(peer);
@ -248,8 +256,14 @@ function GiftComposer({
</div> </div>
)} )}
{isStarGift && gift.upgradeStars && ( {isStarGift && gift.upgradeStars && !areUniqueStarGiftsDisallowed && (
<ListItem className={styles.switcher} narrow ripple onClick={handleShouldPayForUpgradeChange}> <ListItem
className={styles.switcher}
narrow
ripple
onClick={handleShouldPayForUpgradeChange}
disabled={shouldDisallowLimitedStarGifts}
>
<span> <span>
{lang('GiftMakeUnique', { {lang('GiftMakeUnique', {
stars: formatStarsAsIcon(lang, gift.upgradeStars, { className: styles.switcherStarIcon }), stars: formatStarsAsIcon(lang, gift.upgradeStars, { className: styles.switcherStarIcon }),
@ -262,7 +276,7 @@ function GiftComposer({
/> />
</ListItem> </ListItem>
)} )}
{isStarGift && gift.upgradeStars && ( {isStarGift && gift.upgradeStars && !areUniqueStarGiftsDisallowed && (
<div className={styles.description}> <div className={styles.description}>
{isPeerUser {isPeerUser
? lang('GiftMakeUniqueDescription', { ? lang('GiftMakeUniqueDescription', {
@ -388,6 +402,13 @@ export default memo(withGlobal<OwnProps>(
} = selectThemeValues(global, theme) || {}; } = selectThemeValues(global, theme) || {};
const peer = selectPeer(global, peerId); const peer = selectPeer(global, peerId);
const paidMessagesStars = selectPeerPaidMessagesStars(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); const tabState = selectTabState(global);
@ -403,6 +424,8 @@ export default memo(withGlobal<OwnProps>(
currentUserId: global.currentUserId, currentUserId: global.currentUserId,
isPaymentFormLoading: tabState.isPaymentFormLoading, isPaymentFormLoading: tabState.isPaymentFormLoading,
paidMessagesStars, paidMessagesStars,
areUniqueStarGiftsDisallowed,
shouldDisallowLimitedStarGifts,
}; };
}, },
)(GiftComposer)); )(GiftComposer));

View File

@ -5,6 +5,7 @@ import React, {
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { import type {
ApiDisallowedGifts,
ApiPeer, ApiPeer,
ApiPremiumGiftCodeOption, ApiPremiumGiftCodeOption,
ApiStarGiftRegular, ApiStarGiftRegular,
@ -16,7 +17,7 @@ import type { StarGiftCategory } from '../../../types';
import { STARS_CURRENCY_CODE } from '../../../config'; import { STARS_CURRENCY_CODE } from '../../../config';
import { getUserFullName } from '../../../global/helpers'; import { getUserFullName } from '../../../global/helpers';
import { getPeerTitle, isApiPeerChat, isApiPeerUser } from '../../../global/helpers/peers'; 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 buildClassName from '../../../util/buildClassName';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';
@ -54,6 +55,7 @@ type StateProps = {
starBalance?: ApiStarsAmount; starBalance?: ApiStarsAmount;
peer?: ApiPeer; peer?: ApiPeer;
isSelf?: boolean; isSelf?: boolean;
disallowedGifts?: ApiDisallowedGifts;
}; };
const AVATAR_SIZE = 100; const AVATAR_SIZE = 100;
@ -69,6 +71,7 @@ const GiftModal: FC<OwnProps & StateProps> = ({
starBalance, starBalance,
peer, peer,
isSelf, isSelf,
disallowedGifts,
}) => { }) => {
const { const {
closeGiftModal, closeGiftModal,
@ -96,6 +99,20 @@ const GiftModal: FC<OwnProps & StateProps> = ({
const [selectedCategory, setSelectedCategory] = useState<StarGiftCategory>('all'); const [selectedCategory, setSelectedCategory] = useState<StarGiftCategory>('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 oldLang = useOldLang();
const lang = useLang(); const lang = useLang();
const allGifts = renderingModal?.gifts; const allGifts = renderingModal?.gifts;
@ -221,12 +238,31 @@ const GiftModal: FC<OwnProps & StateProps> = ({
}); });
function renderStarGifts() { 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 ( return (
<div className={styles.starGiftsContainer}> <div className={styles.starGiftsContainer}>
{starGiftsById && starGiftIdsByCategory?.[selectedCategory].map((giftId) => { {starGiftsById && filteredGiftIds?.map((giftId) => {
const gift = starGiftsById[giftId]; const gift = starGiftsById[giftId];
return ( return (
<GiftItemStar <GiftItemStar
key={giftId}
gift={gift} gift={gift}
observeIntersection={observeIntersection} observeIntersection={observeIntersection}
onClick={handleGiftClick} onClick={handleGiftClick}
@ -276,20 +312,31 @@ const GiftModal: FC<OwnProps & StateProps> = ({
/> />
<img className={styles.logoBackground} src={StarsBackground} alt="" draggable={false} /> <img className={styles.logoBackground} src={StarsBackground} alt="" draggable={false} />
</div> </div>
{!isSelf && !chat && renderGiftPremiumHeader()} {!isSelf && !chat && !disallowedGifts?.shouldDisallowPremiumGifts && (
{!isSelf && !chat && renderGiftPremiumDescription()} <>
{!isSelf && !chat && renderPremiumGifts()} {renderGiftPremiumHeader()}
{renderGiftPremiumDescription()}
{renderPremiumGifts()}
</>
)}
{renderStarGiftsHeader()} {!areAllGiftsDisallowed && (
{renderStarGiftsDescription()} <>
<StarGiftCategoryList onCategoryChanged={onCategoryChanged} /> {renderStarGiftsHeader()}
<Transition {renderStarGiftsDescription()}
name="zoomFade" <StarGiftCategoryList
activeKey={getCategoryKey(selectedCategory)} areLimitedStarGiftsDisallowed={areLimitedStarGiftsDisallowed}
className={styles.starGiftsTransition} onCategoryChanged={onCategoryChanged}
> />
{renderStarGifts()} <Transition
</Transition> name="zoomFade"
activeKey={getCategoryKey(selectedCategory)}
className={styles.starGiftsTransition}
>
{renderStarGifts()}
</Transition>
</>
)}
</div> </div>
); );
} }
@ -361,6 +408,7 @@ export default memo(withGlobal<OwnProps>((global, { modal }): StateProps => {
const peer = modal?.forPeerId ? selectPeer(global, modal.forPeerId) : undefined; const peer = modal?.forPeerId ? selectPeer(global, modal.forPeerId) : undefined;
const isSelf = Boolean(currentUserId && modal?.forPeerId === currentUserId); const isSelf = Boolean(currentUserId && modal?.forPeerId === currentUserId);
const userFullInfo = peer ? selectUserFullInfo(global, peer?.id) : undefined;
return { return {
boostPerSentGift: global.appConfig?.boostsPerSentGift, boostPerSentGift: global.appConfig?.boostsPerSentGift,
@ -369,6 +417,7 @@ export default memo(withGlobal<OwnProps>((global, { modal }): StateProps => {
starBalance: stars?.balance, starBalance: stars?.balance,
peer, peer,
isSelf, isSelf,
disallowedGifts: userFullInfo?.disallowedGifts,
}; };
})(GiftModal)); })(GiftModal));

View File

@ -20,11 +20,13 @@ type OwnProps = {
type StateProps = { type StateProps = {
idsByCategory?: Record<StarGiftCategory, string[]>; idsByCategory?: Record<StarGiftCategory, string[]>;
areLimitedStarGiftsDisallowed?: boolean;
}; };
const StarGiftCategoryList = ({ const StarGiftCategoryList = ({
idsByCategory, idsByCategory,
onCategoryChanged, onCategoryChanged,
areLimitedStarGiftsDisallowed,
}: StateProps & OwnProps) => { }: StateProps & OwnProps) => {
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
@ -78,7 +80,7 @@ const StarGiftCategoryList = ({
return ( return (
<div ref={ref} className={buildClassName(styles.list, 'no-scrollbar')}> <div ref={ref} className={buildClassName(styles.list, 'no-scrollbar')}>
{renderCategoryItem('all')} {renderCategoryItem('all')}
{renderCategoryItem('limited')} {!areLimitedStarGiftsDisallowed && renderCategoryItem('limited')}
{renderCategoryItem('stock')} {renderCategoryItem('stock')}
{starCategories?.map(renderCategoryItem)} {starCategories?.map(renderCategoryItem)}
</div> </div>

View File

@ -1099,6 +1099,7 @@ async function payInputStarInvoice<T extends GlobalState>(
setGlobal(global); setGlobal(global);
if ('error' in form) { if ('error' in form) {
actions.showDialog({ data: { message: form.error || 'Error', hasErrorKey: true }, tabId });
return; return;
} }

View File

@ -725,6 +725,10 @@ addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload)
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const nonContactPeersPaidStars = payload.nonContactPeersPaidStars === null ? undefined const nonContactPeersPaidStars = payload.nonContactPeersPaidStars === null ? undefined
: payload.nonContactPeersPaidStars || global.settings.byKey.nonContactPeersPaidStars; : 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 // eslint-disable-next-line no-null/no-null
const shouldUpdateUsersSettings = (payload.nonContactPeersPaidStars === null) const shouldUpdateUsersSettings = (payload.nonContactPeersPaidStars === null)
@ -736,6 +740,8 @@ addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload)
shouldHideReadMarks, shouldHideReadMarks,
shouldNewNonContactPeersRequirePremium, shouldNewNonContactPeersRequirePremium,
nonContactPeersPaidStars, nonContactPeersPaidStars,
shouldDisplayGiftsButton,
disallowedGifts,
}); });
setGlobal(global); setGlobal(global);
@ -744,6 +750,8 @@ addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload)
shouldHideReadMarks, shouldHideReadMarks,
shouldNewNonContactPeersRequirePremium, shouldNewNonContactPeersRequirePremium,
nonContactPeersPaidStars, nonContactPeersPaidStars,
shouldDisplayGiftsButton,
disallowedGifts,
}); });
global = getGlobal(); global = getGlobal();
@ -758,6 +766,8 @@ addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload)
nonContactPeersPaidStars: !result nonContactPeersPaidStars: !result
? undefined ? undefined
: result.nonContactPeersPaidStars, : result.nonContactPeersPaidStars,
shouldDisplayGiftsButton: !result ? !shouldDisplayGiftsButton : result.shouldDisplayGiftsButton,
disallowedGifts: !result ? disallowedGifts : result.disallowedGifts,
}); });
if (shouldUpdateUsersSettings) { if (shouldUpdateUsersSettings) {

View File

@ -299,6 +299,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
shouldUpdateStickerSetOrder: true, shouldUpdateStickerSetOrder: true,
shouldArchiveAndMuteNewNonContact: false, shouldArchiveAndMuteNewNonContact: false,
shouldNewNonContactPeersRequirePremium: false, shouldNewNonContactPeersRequirePremium: false,
disallowedGifts: undefined,
nonContactPeersPaidStars: 0, nonContactPeersPaidStars: 0,
shouldHideReadMarks: false, shouldHideReadMarks: false,
canTranslate: false, canTranslate: false,

View File

@ -8,6 +8,7 @@ import type {
ApiChatlistInvite, ApiChatlistInvite,
ApiChatReactions, ApiChatReactions,
ApiChatType, ApiChatType,
ApiDisallowedGiftsSettings,
ApiDraft, ApiDraft,
ApiExportedInvite, ApiExportedInvite,
ApiFormattedText, ApiFormattedText,
@ -2329,6 +2330,8 @@ export interface ActionPayloads {
shouldHideReadMarks?: boolean; shouldHideReadMarks?: boolean;
shouldNewNonContactPeersRequirePremium?: boolean; shouldNewNonContactPeersRequirePremium?: boolean;
nonContactPeersPaidStars?: number | null; nonContactPeersPaidStars?: number | null;
shouldDisplayGiftsButton?: boolean;
disallowedGifts?: ApiDisallowedGiftsSettings;
}; };
// Premium // Premium

View File

@ -9,6 +9,7 @@ import type {
ApiChat, ApiChat,
ApiChatInviteImporter, ApiChatInviteImporter,
ApiContact, ApiContact,
ApiDisallowedGiftsSettings,
ApiDocument, ApiDocument,
ApiDraft, ApiDraft,
ApiExportedInvite, ApiExportedInvite,
@ -147,6 +148,8 @@ export interface AccountSettings {
shouldArchiveAndMuteNewNonContact?: boolean; shouldArchiveAndMuteNewNonContact?: boolean;
shouldNewNonContactPeersRequirePremium?: boolean; shouldNewNonContactPeersRequirePremium?: boolean;
nonContactPeersPaidStars?: number; nonContactPeersPaidStars?: number;
shouldDisplayGiftsButton?: boolean;
disallowedGifts?: ApiDisallowedGiftsSettings;
shouldHideReadMarks?: boolean; shouldHideReadMarks?: boolean;
canTranslate: boolean; canTranslate: boolean;
canTranslateChats: boolean; canTranslateChats: boolean;

View File

@ -1282,7 +1282,16 @@ export interface LangPair {
'PrivacyGifts': undefined; 'PrivacyGifts': undefined;
'PrivacyGiftsTitle': undefined; 'PrivacyGiftsTitle': undefined;
'PrivacyGiftsInfo': undefined; 'PrivacyGiftsInfo': undefined;
'PrivacyAcceptedGiftTitle': undefined;
'PrivacyAcceptedGiftInfo': undefined;
'PrivacyDisplayGiftsButton': undefined;
'PrivacyDisplayGift': undefined;
'SendDisallowError': undefined;
'PrivacyValueBots': undefined; 'PrivacyValueBots': undefined;
'PrivacyGiftLimitedEdition': undefined;
'PrivacyGiftUnlimited': undefined;
'PrivacyGiftUnique': undefined;
'PrivacyGiftPremiumSubscription': undefined;
'CustomShareGiftsInfo': undefined; 'CustomShareGiftsInfo': undefined;
'AllChatsSearchContext': undefined; 'AllChatsSearchContext': undefined;
'PrivateChatsSearchContext': undefined; 'PrivateChatsSearchContext': undefined;
@ -1466,6 +1475,17 @@ export interface LangPair {
'DescriptionRestrictedMedia': undefined; 'DescriptionRestrictedMedia': undefined;
'DescriptionScheduledPaidMediaNotAllowed': undefined; 'DescriptionScheduledPaidMediaNotAllowed': undefined;
'DescriptionScheduledPaidMessagesNotAllowed': 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; 'FrozenAccountModalTitle': undefined;
'FrozenAccountViolationTitle': undefined; 'FrozenAccountViolationTitle': undefined;
'FrozenAccountViolationSubtitle': undefined; 'FrozenAccountViolationSubtitle': undefined;
@ -1555,6 +1575,10 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
'SpeakingWithVolume': { 'SpeakingWithVolume': {
'volume': V; 'volume': V;
}; };
'PrivacyDisplayGiftIconInChats':{
'icon': V;
'gift': V;
};
'CallEmojiKeyTooltip': { 'CallEmojiKeyTooltip': {
'user': V; 'user': V;
}; };

View File

@ -81,6 +81,8 @@ const READABLE_ERROR_MESSAGES: Record<string, string> = {
PEERS_LIST_EMPTY: 'No chats are added to the list', PEERS_LIST_EMPTY: 'No chats are added to the list',
PAID_MEDIA_FORBIDDEN: 'You can\'t send paid media in this chat', PAID_MEDIA_FORBIDDEN: 'You can\'t send paid media in this chat',
USER_DISALLOWED_STARGIFTS: 'User is not accepting gifts',
}; };
if (DEBUG) { if (DEBUG) {