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 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,
};
}

View File

@ -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),

View File

@ -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': {

View File

@ -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;
}
}

View File

@ -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,
};
}

View File

@ -215,3 +215,10 @@ export interface ApiStarsGiveawayWinnerOption {
users: 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;
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;
}

View File

@ -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?";

View File

@ -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<OwnProps & StateProps> = ({
isRightColumnShown,
isSelectModeActive,
isReactionPickerOpen,
shouldDisplayGiftsButton,
isForwarding,
forwardedMessagesCount,
pollModal,
@ -414,6 +418,7 @@ const Composer: FC<OwnProps & StateProps> = ({
isPaymentMessageConfirmDialogOpen,
starsBalance,
isStarsBalanceModalOpen,
disallowedGifts,
isAccountFrozen,
isAppConfigLoaded,
}) => {
@ -434,6 +439,7 @@ const Composer: FC<OwnProps & StateProps> = ({
showNotification,
showAllowedMessageTypesNotification,
openStoryReactionPicker,
openGiftModal,
closeReactionPicker,
sendStoryReaction,
editMessage,
@ -835,6 +841,15 @@ const Composer: FC<OwnProps & StateProps> = ({
};
}, [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<OwnProps & StateProps> = ({
});
});
const handleGiftClick = useLastCallback(() => {
openGiftModal({ forUserId: chatId });
});
const handleToggleSilentPosting = useLastCallback(() => {
const newValue = !isSilentPosting;
updateChatSilentPosting({ chatId, isEnabled: newValue });
@ -2061,6 +2080,17 @@ const Composer: FC<OwnProps & StateProps> = ({
<Icon name="schedule" />
</Button>
)}
{shouldShowGiftButton && (
<Button
round
faded
className="composer-action-button"
color="translucent"
onClick={handleGiftClick}
>
<Icon name="gift" />
</Button>
)}
{Boolean(botKeyboardMessageId) && !activeVoiceRecording && !editingMessage && (
<ResponsiveHoverButton
className={buildClassName('composer-action-button', isBotKeyboardOpen && 'activated')}
@ -2464,6 +2494,8 @@ export default memo(withGlobal<OwnProps>(
isPaymentMessageConfirmDialogOpen: tabState.isPaymentMessageConfirmDialogOpen,
starsBalance,
isStarsBalanceModalOpen,
shouldDisplayGiftsButton: userFullInfo?.shouldDisplayGiftsButton,
disallowedGifts: userFullInfo?.disallowedGifts,
isAccountFrozen,
isAppConfigLoaded,
};

View File

@ -188,6 +188,10 @@
&[dir="rtl"] {
text-align: right;
}
.gift-icon {
vertical-align: text-top;
}
}
.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 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<OwnProps & StateProps> = ({
@ -47,12 +52,36 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
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<OwnProps & StateProps> = ({
return (
<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
screen={screen}
privacy={primaryPrivacy}
@ -83,6 +133,9 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
{screen === SettingsScreens.PrivacyLastSeen && (
<SettingsPrivacyLastSeen visibility={primaryPrivacy?.visibility} />
)}
{screen === SettingsScreens.PrivacyGifts && (
<SettingsAcceptedGift />
)}
{secondaryScreen && (
<PrivacySubsection
screen={secondaryScreen}
@ -361,7 +414,12 @@ export default memo(withGlobal<OwnProps>(
const {
currentUserId,
settings: { privacy },
settings: {
privacy,
byKey: {
shouldDisplayGiftsButton,
},
},
} = global;
const currentUserFullInfo = selectUserFullInfo(global, currentUserId!);
@ -426,6 +484,8 @@ export default memo(withGlobal<OwnProps>(
hasCurrentUserFullInfo: Boolean(currentUserFullInfo),
currentUserFallbackPhoto: currentUserFullInfo?.fallbackPhoto,
isPremiumRequired: screen === SettingsScreens.PrivacyVoiceMessages && !selectIsCurrentUserPremium(global),
shouldDisplayGiftsButton,
isCurrentUserPremium: selectIsCurrentUserPremium(global),
};
},
)(SettingsPrivacyVisibility));

View File

@ -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<OwnProps & StateProps> = ({
onAsMessagesClick,
onClose,
onCloseAnimationEnd,
disallowedGifts,
isAccountFrozen,
}) => {
const {
@ -207,8 +213,12 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
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<OwnProps & StateProps> = ({
(!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<OwnProps & StateProps> = ({
});
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<OwnProps & StateProps> = ({
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<OwnProps & StateProps> = ({
// eslint-disable-next-line react/jsx-no-bind
onClick={handleClick}
>
{lang(cmd.label)}
{oldLang(cmd.label)}
</MenuItem>
);
});
@ -503,39 +523,39 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
if (hasPrivacyCommand && !botPrivacyPolicyUrl) {
sendBotCommand({ command: '/privacy' });
} else {
openUrl({ url: botPrivacyPolicyUrl || lang('BotDefaultPrivacyPolicy') });
openUrl({ url: botPrivacyPolicyUrl || oldLang('BotDefaultPrivacyPolicy') });
}
closeMenu();
}}
>
{lang('BotPrivacyPolicy')}
{oldLang('BotPrivacyPolicy')}
</MenuItem>
);
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 (
<Portal>
@ -552,7 +572,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="search"
onClick={handleSearch}
>
{lang('Search')}
{oldLang('Search')}
</MenuItem>
)}
{withForumActions && canCreateTopic && (
@ -561,7 +581,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="comments"
onClick={handleCreateTopicClick}
>
{lang('lng_forum_create_topic')}
{oldLang('lng_forum_create_topic')}
</MenuItem>
<MenuSeparator />
</>
@ -571,7 +591,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
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')}
</MenuItem>
)}
{canManage && !canEditTopic && (
@ -579,7 +599,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="edit"
onClick={handleEditClick}
>
{lang('Edit')}
{oldLang('Edit')}
</MenuItem>
)}
{canEditTopic && (
@ -587,7 +607,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="edit"
onClick={handleEditTopicClick}
>
{lang('lng_forum_topic_edit')}
{oldLang('lng_forum_topic_edit')}
</MenuItem>
)}
{isMobile && !withForumActions && isForum && !isTopic && (
@ -595,7 +615,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="forums"
onClick={handleViewAsTopicsClick}
>
{lang('Chat.ContextViewAsTopics')}
{oldLang('Chat.ContextViewAsTopics')}
</MenuItem>
)}
{withForumActions && Boolean(pendingJoinRequests) && (
@ -603,7 +623,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="user"
onClick={onJoinRequestsClick}
>
{isChannel ? lang('SubscribeRequests') : lang('MemberRequests')}
{isChannel ? oldLang('SubscribeRequests') : oldLang('MemberRequests')}
<div className="right-badge">{pendingJoinRequests}</div>
</MenuItem>
)}
@ -612,7 +632,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="message"
onClick={handleOpenAsMessages}
>
{lang('lng_forum_view_as_messages')}
{oldLang('lng_forum_view_as_messages')}
</MenuItem>
)}
{withExtraActions && canStartBot && (
@ -620,7 +640,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="bots"
onClick={handleStartBot}
>
{lang('BotStart')}
{oldLang('BotStart')}
</MenuItem>
)}
{withExtraActions && canSubscribe && (
@ -628,7 +648,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon={isChannel ? 'channel' : 'group'}
onClick={handleSubscribe}
>
{lang(isChannel ? 'ProfileJoinChannel' : 'ProfileJoinGroup')}
{oldLang(isChannel ? 'ProfileJoinChannel' : 'ProfileJoinGroup')}
</MenuItem>
)}
{canShowBoostModal && !canViewBoosts && (
@ -636,7 +656,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="boost-outline"
onClick={handleBoostClick}
>
{lang(isChannel ? 'BoostingBoostChannelMenu' : 'BoostingBoostGroupMenu')}
{oldLang(isChannel ? 'BoostingBoostChannelMenu' : 'BoostingBoostGroupMenu')}
</MenuItem>
)}
{canAddContact && (
@ -644,7 +664,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="add-user"
onClick={handleAddContactClick}
>
{lang('AddContact')}
{oldLang('AddContact')}
</MenuItem>
)}
{isMobile && canCall && (
@ -652,7 +672,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="phone"
onClick={handleCall}
>
{lang('Call')}
{oldLang('Call')}
</MenuItem>
)}
{canCall && (
@ -660,7 +680,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="video-outlined"
onClick={handleVideoCall}
>
{lang('VideoCall')}
{oldLang('VideoCall')}
</MenuItem>
)}
{canMute && (isMuted ? (
@ -668,7 +688,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="unmute"
onClick={handleUnmuteClick}
>
{lang('ChatsUnmute')}
{oldLang('ChatsUnmute')}
</MenuItem>
)
: (
@ -676,7 +696,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="mute"
onClick={handleMuteClick}
>
{lang('ChatsMute')}...
{oldLang('ChatsMute')}...
</MenuItem>
)
)}
@ -685,7 +705,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="voice-chat"
onClick={handleEnterVoiceChatClick}
>
{lang(canCreateVoiceChat ? 'StartVoipChat' : 'VoipGroupJoinCall')}
{oldLang(canCreateVoiceChat ? 'StartVoipChat' : 'VoipGroupJoinCall')}
</MenuItem>
)}
{hasLinkedChat && (
@ -693,7 +713,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon={isChannel ? 'comments' : 'channel'}
onClick={handleLinkedChatClick}
>
{lang(isChannel ? 'ViewDiscussion' : 'lng_profile_view_channel')}
{oldLang(isChannel ? 'ViewDiscussion' : 'lng_profile_view_channel')}
</MenuItem>
)}
{!withForumActions && (
@ -701,7 +721,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="select"
onClick={handleSelectMessages}
>
{lang('ReportSelectMessages')}
{oldLang('ReportSelectMessages')}
</MenuItem>
)}
{canViewBoosts && (
@ -709,7 +729,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="boost-outline"
onClick={handleBoostClick}
>
{lang('Boosts')}
{oldLang('Boosts')}
</MenuItem>
)}
{canViewStatistics && (
@ -717,7 +737,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="stats"
onClick={handleStatisticsClick}
>
{lang('Statistics')}
{oldLang('Statistics')}
</MenuItem>
)}
{isChannel && canViewMonetization && (
@ -725,7 +745,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="cash-circle"
onClick={handleMonetizationClick}
>
{lang('lng_channel_earn_title')}
{oldLang('lng_channel_earn_title')}
</MenuItem>
)}
{canTranslate && (
@ -733,7 +753,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="language"
onClick={handleEnableTranslations}
>
{lang('lng_context_translate')}
{oldLang('lng_context_translate')}
</MenuItem>
)}
{canReportChat && (
@ -741,7 +761,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="flag"
onClick={handleReport}
>
{lang('ReportPeer.Report')}
{oldLang('ReportPeer.Report')}
</MenuItem>
)}
{botButtons}
@ -750,7 +770,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon="gift"
onClick={handleGiftClick}
>
{lang('ProfileSendAGift')}
{oldLang('ProfileSendAGift')}
</MenuItem>
)}
{isBot && (
@ -758,7 +778,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon={isBlocked ? 'bots' : 'hand-stop'}
onClick={isBlocked ? handleRestartBot : handleBlock}
>
{isBlocked ? lang('BotRestart') : lang('Bot.Stop')}
{isBlocked ? oldLang('BotRestart') : oldLang('Bot.Stop')}
</MenuItem>
)}
{isPrivate && !isChatWithSelf && !isBot && (
@ -766,7 +786,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
icon={isBlocked ? 'user' : 'hand-stop'}
onClick={isBlocked ? handleUnblock : handleBlock}
>
{isBlocked ? lang('Unblock') : lang('BlockUser')}
{isBlocked ? oldLang('Unblock') : oldLang('BlockUser')}
</MenuItem>
)}
{canLeave && (
@ -861,6 +881,7 @@ export default memo(withGlobal<OwnProps>(
isBot: Boolean(chatBot),
isChatWithSelf,
savedDialog,
disallowedGifts: userFullInfo?.disallowedGifts,
isAccountFrozen,
};
},

View File

@ -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({
</div>
)}
{isStarGift && gift.upgradeStars && (
<ListItem className={styles.switcher} narrow ripple onClick={handleShouldPayForUpgradeChange}>
{isStarGift && gift.upgradeStars && !areUniqueStarGiftsDisallowed && (
<ListItem
className={styles.switcher}
narrow
ripple
onClick={handleShouldPayForUpgradeChange}
disabled={shouldDisallowLimitedStarGifts}
>
<span>
{lang('GiftMakeUnique', {
stars: formatStarsAsIcon(lang, gift.upgradeStars, { className: styles.switcherStarIcon }),
@ -262,7 +276,7 @@ function GiftComposer({
/>
</ListItem>
)}
{isStarGift && gift.upgradeStars && (
{isStarGift && gift.upgradeStars && !areUniqueStarGiftsDisallowed && (
<div className={styles.description}>
{isPeerUser
? lang('GiftMakeUniqueDescription', {
@ -388,6 +402,13 @@ export default memo(withGlobal<OwnProps>(
} = 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<OwnProps>(
currentUserId: global.currentUserId,
isPaymentFormLoading: tabState.isPaymentFormLoading,
paidMessagesStars,
areUniqueStarGiftsDisallowed,
shouldDisallowLimitedStarGifts,
};
},
)(GiftComposer));

View File

@ -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<OwnProps & StateProps> = ({
starBalance,
peer,
isSelf,
disallowedGifts,
}) => {
const {
closeGiftModal,
@ -96,6 +99,20 @@ const GiftModal: FC<OwnProps & StateProps> = ({
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 lang = useLang();
const allGifts = renderingModal?.gifts;
@ -221,12 +238,31 @@ const GiftModal: FC<OwnProps & StateProps> = ({
});
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 (
<div className={styles.starGiftsContainer}>
{starGiftsById && starGiftIdsByCategory?.[selectedCategory].map((giftId) => {
{starGiftsById && filteredGiftIds?.map((giftId) => {
const gift = starGiftsById[giftId];
return (
<GiftItemStar
key={giftId}
gift={gift}
observeIntersection={observeIntersection}
onClick={handleGiftClick}
@ -276,20 +312,31 @@ const GiftModal: FC<OwnProps & StateProps> = ({
/>
<img className={styles.logoBackground} src={StarsBackground} alt="" draggable={false} />
</div>
{!isSelf && !chat && renderGiftPremiumHeader()}
{!isSelf && !chat && renderGiftPremiumDescription()}
{!isSelf && !chat && renderPremiumGifts()}
{!isSelf && !chat && !disallowedGifts?.shouldDisallowPremiumGifts && (
<>
{renderGiftPremiumHeader()}
{renderGiftPremiumDescription()}
{renderPremiumGifts()}
</>
)}
{renderStarGiftsHeader()}
{renderStarGiftsDescription()}
<StarGiftCategoryList onCategoryChanged={onCategoryChanged} />
<Transition
name="zoomFade"
activeKey={getCategoryKey(selectedCategory)}
className={styles.starGiftsTransition}
>
{renderStarGifts()}
</Transition>
{!areAllGiftsDisallowed && (
<>
{renderStarGiftsHeader()}
{renderStarGiftsDescription()}
<StarGiftCategoryList
areLimitedStarGiftsDisallowed={areLimitedStarGiftsDisallowed}
onCategoryChanged={onCategoryChanged}
/>
<Transition
name="zoomFade"
activeKey={getCategoryKey(selectedCategory)}
className={styles.starGiftsTransition}
>
{renderStarGifts()}
</Transition>
</>
)}
</div>
);
}
@ -361,6 +408,7 @@ export default memo(withGlobal<OwnProps>((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<OwnProps>((global, { modal }): StateProps => {
starBalance: stars?.balance,
peer,
isSelf,
disallowedGifts: userFullInfo?.disallowedGifts,
};
})(GiftModal));

View File

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

View File

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

View File

@ -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) {

View File

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

View File

@ -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

View File

@ -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;

View File

@ -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<V extends unknown = LangVariable> {
'SpeakingWithVolume': {
'volume': V;
};
'PrivacyDisplayGiftIconInChats':{
'icon': V;
'gift': V;
};
'CallEmojiKeyTooltip': {
'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',
PAID_MEDIA_FORBIDDEN: 'You can\'t send paid media in this chat',
USER_DISALLOWED_STARGIFTS: 'User is not accepting gifts',
};
if (DEBUG) {