Paid Messages: Follow up (#5790)

This commit is contained in:
Alexander Zinchuk 2025-04-04 13:04:13 +02:00
parent 71b7c9e9bb
commit 3055df600b
78 changed files with 468 additions and 248 deletions

View File

@ -9,7 +9,7 @@ import type {
} from '../../../lib/secret-sauce'; } from '../../../lib/secret-sauce';
import type { ApiGroupCall, ApiPhoneCall } from '../../types'; import type { ApiGroupCall, ApiPhoneCall } from '../../types';
import { getApiChatIdFromMtpPeer, isPeerUser } from './peers'; import { getApiChatIdFromMtpPeer, isMtpPeerUser } from './peers';
export function buildApiGroupCallParticipant(participant: GramJs.GroupCallParticipant): GroupCallParticipant { export function buildApiGroupCallParticipant(participant: GramJs.GroupCallParticipant): GroupCallParticipant {
const { const {
@ -33,7 +33,7 @@ export function buildApiGroupCallParticipant(participant: GramJs.GroupCallPartic
raiseHandRating: raiseHandRating?.toString(), raiseHandRating: raiseHandRating?.toString(),
volume, volume,
date: new Date(date), date: new Date(date),
isUser: isPeerUser(peer), isUser: isMtpPeerUser(peer),
id: getApiChatIdFromMtpPeer(peer), id: getApiChatIdFromMtpPeer(peer),
video: video ? buildApiGroupCallParticipantVideo(video) : undefined, video: video ? buildApiGroupCallParticipantVideo(video) : undefined,
presentation: presentation ? buildApiGroupCallParticipantVideo(presentation) : undefined, presentation: presentation ? buildApiGroupCallParticipantVideo(presentation) : undefined,

View File

@ -36,8 +36,8 @@ import {
buildApiPeerColor, buildApiPeerColor,
buildApiPeerId, buildApiPeerId,
getApiChatIdFromMtpPeer, getApiChatIdFromMtpPeer,
isPeerChat, isMtpPeerChat,
isPeerUser, isMtpPeerUser,
} from './peers'; } from './peers';
import { buildApiReaction } from './reactions'; import { buildApiReaction } from './reactions';
@ -295,9 +295,9 @@ export function getApiChatTypeFromPeerEntity(peerEntity: GramJs.TypeChat | GramJ
} }
export function getPeerKey(peer: GramJs.TypePeer) { export function getPeerKey(peer: GramJs.TypePeer) {
if (isPeerUser(peer)) { if (isMtpPeerUser(peer)) {
return `user${peer.userId}`; return `user${peer.userId}`;
} else if (isPeerChat(peer)) { } else if (isMtpPeerChat(peer)) {
return `chat${peer.chatId}`; return `chat${peer.chatId}`;
} else { } else {
return `chat${peer.channelId}`; return `chat${peer.channelId}`;
@ -305,7 +305,7 @@ export function getPeerKey(peer: GramJs.TypePeer) {
} }
export function getApiChatTitleFromMtpPeer(peer: GramJs.TypePeer, peerEntity: GramJs.User | GramJs.Chat) { export function getApiChatTitleFromMtpPeer(peer: GramJs.TypePeer, peerEntity: GramJs.User | GramJs.Chat) {
if (isPeerUser(peer)) { if (isMtpPeerUser(peer)) {
return getUserName(peerEntity as GramJs.User); return getUserName(peerEntity as GramJs.User);
} else { } else {
return (peerEntity as GramJs.Chat).title; return (peerEntity as GramJs.Chat).title;

View File

@ -6,15 +6,15 @@ import type { ApiEmojiStatusType, ApiPeerColor } from '../../types';
import { CHANNEL_ID_LENGTH } from '../../../config'; import { CHANNEL_ID_LENGTH } from '../../../config';
import { numberToHexColor } from '../../../util/colors'; import { numberToHexColor } from '../../../util/colors';
export function isPeerUser(peer: GramJs.TypePeer | GramJs.TypeInputPeer): peer is GramJs.PeerUser { export function isMtpPeerUser(peer: GramJs.TypePeer | GramJs.TypeInputPeer): peer is GramJs.PeerUser {
return peer.hasOwnProperty('userId'); return peer.hasOwnProperty('userId');
} }
export function isPeerChat(peer: GramJs.TypePeer | GramJs.TypeInputPeer): peer is GramJs.PeerChat { export function isMtpPeerChat(peer: GramJs.TypePeer | GramJs.TypeInputPeer): peer is GramJs.PeerChat {
return peer.hasOwnProperty('chatId'); return peer.hasOwnProperty('chatId');
} }
export function isPeerChannel(peer: GramJs.TypePeer | GramJs.TypeInputPeer): peer is GramJs.PeerChannel { export function isMtpPeerChannel(peer: GramJs.TypePeer | GramJs.TypeInputPeer): peer is GramJs.PeerChannel {
return peer.hasOwnProperty('channelId'); return peer.hasOwnProperty('channelId');
} }
@ -34,9 +34,9 @@ export function buildApiPeerId(id: BigInt.BigInteger, type: 'user' | 'chat' | 'c
} }
export function getApiChatIdFromMtpPeer(peer: GramJs.TypePeer | GramJs.TypeInputPeer) { export function getApiChatIdFromMtpPeer(peer: GramJs.TypePeer | GramJs.TypeInputPeer) {
if (isPeerUser(peer)) { if (isMtpPeerUser(peer)) {
return buildApiPeerId(peer.userId, 'user'); return buildApiPeerId(peer.userId, 'user');
} else if (isPeerChat(peer)) { } else if (isMtpPeerChat(peer)) {
return buildApiPeerId(peer.chatId, 'chat'); return buildApiPeerId(peer.chatId, 'chat');
} else { } else {
return buildApiPeerId((peer as GramJs.InputPeerChannel).channelId, 'channel'); return buildApiPeerId((peer as GramJs.InputPeerChannel).channelId, 'channel');

View File

@ -258,6 +258,17 @@ export async function addNoPaidMessagesException({ user, shouldRefundCharged }:
return result; return result;
} }
export async function fetchPaidMessagesRevenue({ user }: {
user: ApiUser;
shouldRefundCharged?: boolean;
}) {
const result = await invokeRequest(new GramJs.account.GetPaidMessagesRevenue({
userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
}));
if (!result) return undefined;
return result.starsAmount.toJSNumber();
}
export async function fetchProfilePhotos({ export async function fetchProfilePhotos({
peer, peer,
offset = 0, offset = 0,

View File

@ -1890,4 +1890,7 @@
"PaidMessageTransaction_one" = "Fee for {count} Message"; "PaidMessageTransaction_one" = "Fee for {count} Message";
"PaidMessageTransaction_other" = "Fee for {count} Messages"; "PaidMessageTransaction_other" = "Fee for {count} Messages";
"PaidMessageTransactionDescription" = "You receive **{percent}** of the price that you charge for each incoming message."; "PaidMessageTransactionDescription" = "You receive **{percent}** of the price that you charge for each incoming message.";
"PaidMessageTransactionTotal" = "Total"; "PaidMessageTransactionTotal" = "Total";
"DescriptionRestrictedMedia" = "Posting media content is not allowed in this group.";
"DescriptionScheduledPaidMediaNotAllowed" = "Posting scheduled paid media content is not allowed";
"DescriptionScheduledPaidMessagesNotAllowed" = "Scheduled paid messages is not allowed";

View File

@ -12,3 +12,4 @@ export { default as GiftUpgradeModal } from '../components/modals/gift/upgrade/G
export { default as GiftStatusInfoModal } from '../components/modals/gift/status/GiftStatusInfoModal'; export { default as GiftStatusInfoModal } from '../components/modals/gift/status/GiftStatusInfoModal';
export { default as GiftWithdrawModal } from '../components/modals/gift/withdraw/GiftWithdrawModal'; export { default as GiftWithdrawModal } from '../components/modals/gift/withdraw/GiftWithdrawModal';
export { default as GiftTransferModal } from '../components/modals/gift/transfer/GiftTransferModal'; export { default as GiftTransferModal } from '../components/modals/gift/transfer/GiftTransferModal';
export { default as ChatRefundModal } from '../components/modals/stars/chatRefund/ChatRefundModal';

View File

@ -21,10 +21,9 @@ import {
isAnonymousForwardsChat, isAnonymousForwardsChat,
isChatWithRepliesBot, isChatWithRepliesBot,
isDeletedUser, isDeletedUser,
isPeerChat,
isPeerUser,
isUserId, isUserId,
} from '../../global/helpers'; } from '../../global/helpers';
import { isApiPeerChat, isApiPeerUser } from '../../global/helpers/peers';
import buildClassName, { createClassNameBuilder } from '../../util/buildClassName'; import buildClassName, { createClassNameBuilder } from '../../util/buildClassName';
import buildStyle from '../../util/buildStyle'; import buildStyle from '../../util/buildStyle';
import { getFirstLetters } from '../../util/textFormat'; import { getFirstLetters } from '../../util/textFormat';
@ -117,8 +116,8 @@ const Avatar: FC<OwnProps> = ({
const videoLoopCountRef = useRef(0); const videoLoopCountRef = useRef(0);
const isCustomPeer = peer && 'isCustomPeer' in peer; const isCustomPeer = peer && 'isCustomPeer' in peer;
const realPeer = peer && !isCustomPeer ? peer : undefined; const realPeer = peer && !isCustomPeer ? peer : undefined;
const user = realPeer && isPeerUser(realPeer) ? realPeer : undefined; const user = realPeer && isApiPeerUser(realPeer) ? realPeer : undefined;
const chat = realPeer && isPeerChat(realPeer) ? realPeer : undefined; const chat = realPeer && isApiPeerChat(realPeer) ? realPeer : undefined;
const isDeleted = user && isDeletedUser(user); const isDeleted = user && isDeletedUser(user);
const isReplies = realPeer && isChatWithRepliesBot(realPeer.id); const isReplies = realPeer && isChatWithRepliesBot(realPeer.id);
const isAnonymousForwards = realPeer && isAnonymousForwardsChat(realPeer.id); const isAnonymousForwards = realPeer && isAnonymousForwardsChat(realPeer.id);

View File

@ -57,7 +57,6 @@ import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterd
import { import {
canEditMedia, canEditMedia,
getAllowedAttachmentOptions, getAllowedAttachmentOptions,
getPeerTitle,
getReactionKey, getReactionKey,
getStoryKey, getStoryKey,
isChatAdmin, isChatAdmin,
@ -68,6 +67,7 @@ import {
isUserId, isUserId,
} from '../../global/helpers'; } from '../../global/helpers';
import { getChatNotifySettings } from '../../global/helpers/notifications'; import { getChatNotifySettings } from '../../global/helpers/notifications';
import { getPeerTitle } from '../../global/helpers/peers';
import { import {
selectBot, selectBot,
selectCanPlayAnimatedEmojis, selectCanPlayAnimatedEmojis,
@ -288,6 +288,8 @@ type StateProps =
shouldPaidMessageAutoApprove?: boolean; shouldPaidMessageAutoApprove?: boolean;
isSilentPosting?: boolean; isSilentPosting?: boolean;
isPaymentMessageConfirmDialogOpen: boolean; isPaymentMessageConfirmDialogOpen: boolean;
starsBalance: number;
isStarsBalanceModalOpen: boolean;
}; };
enum MainButtonState { enum MainButtonState {
@ -406,6 +408,8 @@ const Composer: FC<OwnProps & StateProps> = ({
onBlur, onBlur,
onForward, onForward,
isPaymentMessageConfirmDialogOpen, isPaymentMessageConfirmDialogOpen,
starsBalance,
isStarsBalanceModalOpen,
}) => { }) => {
const { const {
sendMessage, sendMessage,
@ -520,8 +524,13 @@ const Composer: FC<OwnProps & StateProps> = ({
canSendStickers, canSendGifs, canAttachMedia, canAttachPolls, canAttachEmbedLinks, canSendStickers, canSendGifs, canAttachMedia, canAttachPolls, canAttachEmbedLinks,
canSendVoices, canSendPlainText, canSendAudios, canSendVideos, canSendPhotos, canSendDocuments, canSendVoices, canSendPlainText, canSendAudios, canSendVideos, canSendPhotos, canSendDocuments,
} = useMemo( } = useMemo(
() => getAllowedAttachmentOptions(chat, chatFullInfo, isChatWithBot, isInStoryViewer), () => getAllowedAttachmentOptions(chat,
[chat, chatFullInfo, isChatWithBot, isInStoryViewer], chatFullInfo,
isChatWithBot,
isInStoryViewer,
paidMessagesStars,
isInScheduledList),
[chat, chatFullInfo, isChatWithBot, isInStoryViewer, paidMessagesStars, isInScheduledList],
); );
const isNeedPremium = isContactRequirePremium && isInStoryViewer; const isNeedPremium = isContactRequirePremium && isInStoryViewer;
@ -541,7 +550,7 @@ const Composer: FC<OwnProps & StateProps> = ({
shouldAutoApprove: shouldPaidMessageAutoApprove, shouldAutoApprove: shouldPaidMessageAutoApprove,
setAutoApprove: setShouldPaidMessageAutoApprove, setAutoApprove: setShouldPaidMessageAutoApprove,
handleWithConfirmation: handleActionWithPaymentConfirmation, handleWithConfirmation: handleActionWithPaymentConfirmation,
} = usePaidMessageConfirmation(starsForAllMessages); } = usePaidMessageConfirmation(starsForAllMessages, isStarsBalanceModalOpen, starsBalance);
const hasWebPagePreview = !hasAttachments && canAttachEmbedLinks && !noWebPage && Boolean(webPagePreview); const hasWebPagePreview = !hasAttachments && canAttachEmbedLinks && !noWebPage && Boolean(webPagePreview);
const isComposerBlocked = isSendTextBlocked && !editingMessage; const isComposerBlocked = isSendTextBlocked && !editingMessage;
@ -1391,19 +1400,23 @@ const Composer: FC<OwnProps & StateProps> = ({
if (isInScheduledList) { if (isInScheduledList) {
requestCalendar((scheduledAt) => { requestCalendar((scheduledAt) => {
handleMessageSchedule({ poll }, scheduledAt, currentMessageList); handleActionWithPaymentConfirmation(
handleMessageSchedule,
{ poll },
scheduledAt,
currentMessageList,
);
}); });
closePollModal(); closePollModal();
} else { } else {
sendMessage({ messageList: currentMessageList, poll, isSilent: isSilentPosting }); handleActionWithPaymentConfirmation(
sendMessage,
{ messageList: currentMessageList, poll, isSilent: isSilentPosting },
);
closePollModal(); closePollModal();
} }
}); });
const handlePollSendWithPaymentConfirmation = useLastCallback((poll: ApiNewPoll) => {
handleActionWithPaymentConfirmation(handlePollSend, poll);
});
const sendSilent = useLastCallback((additionalArgs?: ScheduledMessageArgs) => { const sendSilent = useLastCallback((additionalArgs?: ScheduledMessageArgs) => {
if (isInScheduledList) { if (isInScheduledList) {
requestCalendar((scheduledAt) => { requestCalendar((scheduledAt) => {
@ -1587,7 +1600,7 @@ const Composer: FC<OwnProps & StateProps> = ({
message: oldLang('VoiceMessagesRestrictedByPrivacy', chat?.title), message: oldLang('VoiceMessagesRestrictedByPrivacy', chat?.title),
}); });
} else if (!canSendVoices) { } else if (!canSendVoices) {
showAllowedMessageTypesNotification({ chatId }); showAllowedMessageTypesNotification({ chatId, messageListType });
} }
} else { } else {
setIsViewOnceEnabled(false); setIsViewOnceEnabled(false);
@ -1812,7 +1825,7 @@ const Composer: FC<OwnProps & StateProps> = ({
isQuiz={pollModal.isQuiz} isQuiz={pollModal.isQuiz}
shouldBeAnonymous={isChannel} shouldBeAnonymous={isChannel}
onClear={closePollModal} onClear={closePollModal}
onSend={handlePollSendWithPaymentConfirmation} onSend={handlePollSend}
/> />
<SendAsMenu <SendAsMenu
isOpen={isSendAsMenuOpen} isOpen={isSendAsMenuOpen}
@ -1998,6 +2011,7 @@ const Composer: FC<OwnProps & StateProps> = ({
onFocus={markInputHasFocus} onFocus={markInputHasFocus}
onBlur={unmarkInputHasFocus} onBlur={unmarkInputHasFocus}
isNeedPremium={isNeedPremium} isNeedPremium={isNeedPremium}
messageListType={messageListType}
/> />
{isInMessageList && ( {isInMessageList && (
<> <>
@ -2084,6 +2098,8 @@ const Composer: FC<OwnProps & StateProps> = ({
theme={theme} theme={theme}
onMenuOpen={onAttachMenuOpen} onMenuOpen={onAttachMenuOpen}
onMenuClose={onAttachMenuClose} onMenuClose={onAttachMenuClose}
messageListType={messageListType}
paidMessagesStars={paidMessagesStars}
/> />
)} )}
{isInMessageList && Boolean(botKeyboardMessageId) && ( {isInMessageList && Boolean(botKeyboardMessageId) && (
@ -2354,6 +2370,8 @@ export default memo(withGlobal<OwnProps>(
const maxMessageLength = global.config?.maxMessageLength || DEFAULT_MAX_MESSAGE_LENGTH; const maxMessageLength = global.config?.maxMessageLength || DEFAULT_MAX_MESSAGE_LENGTH;
const isForwarding = chatId === tabState.forwardMessages.toChatId; const isForwarding = chatId === tabState.forwardMessages.toChatId;
const starsBalance = global.stars?.balance.amount || 0;
const isStarsBalanceModalOpen = Boolean(tabState.starsBalanceModal);
return { return {
availableReactions: global.reactions.availableReactions, availableReactions: global.reactions.availableReactions,
@ -2436,6 +2454,8 @@ export default memo(withGlobal<OwnProps>(
shouldPaidMessageAutoApprove, shouldPaidMessageAutoApprove,
isSilentPosting, isSilentPosting,
isPaymentMessageConfirmDialogOpen: tabState.isPaymentMessageConfirmDialogOpen, isPaymentMessageConfirmDialogOpen: tabState.isPaymentMessageConfirmDialogOpen,
starsBalance,
isStarsBalanceModalOpen,
}; };
}, },
)(Composer)); )(Composer));

View File

@ -12,7 +12,6 @@ import type { IRadioOption } from '../ui/CheckboxGroup';
import { import {
getHasAdminRight, getHasAdminRight,
getPeerTitle,
getPrivateChatUserId, getPrivateChatUserId,
getUserFirstOrLastName, isChatBasicGroup, getUserFirstOrLastName, isChatBasicGroup,
isChatChannel, isChatChannel,
@ -20,6 +19,7 @@ import {
isSystemBot, isSystemBot,
isUserId, isUserId,
} from '../../global/helpers'; } from '../../global/helpers';
import { getPeerTitle } from '../../global/helpers/peers';
import { import {
getSendersFromSelectedMessages, getSendersFromSelectedMessages,
selectBot, selectBot,

View File

@ -15,8 +15,8 @@ import {
isAnonymousForwardsChat, isAnonymousForwardsChat,
isChatWithRepliesBot, isChatWithRepliesBot,
isChatWithVerificationCodesBot, isChatWithVerificationCodesBot,
isPeerUser,
} from '../../global/helpers'; } from '../../global/helpers';
import { isApiPeerUser } from '../../global/helpers/peers';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import buildStyle from '../../util/buildStyle'; import buildStyle from '../../util/buildStyle';
import { copyTextToClipboard } from '../../util/clipboard'; import { copyTextToClipboard } from '../../util/clipboard';
@ -71,7 +71,7 @@ const FullNameTitle: FC<OwnProps> = ({
const { showNotification } = getActions(); const { showNotification } = getActions();
const realPeer = 'id' in peer ? peer : undefined; const realPeer = 'id' in peer ? peer : undefined;
const customPeer = 'isCustomPeer' in peer ? peer : undefined; const customPeer = 'isCustomPeer' in peer ? peer : undefined;
const isUser = realPeer && isPeerUser(realPeer); const isUser = realPeer && isApiPeerUser(realPeer);
const title = realPeer && (isUser ? getUserFullName(realPeer) : getChatTitle(lang, realPeer)); const title = realPeer && (isUser ? getUserFullName(realPeer) : getChatTitle(lang, realPeer));
const isPremium = isUser && realPeer.isPremium; const isPremium = isUser && realPeer.isPremium;
const canShowEmojiStatus = withEmojiStatus && !isSavedMessages && realPeer; const canShowEmojiStatus = withEmojiStatus && !isSavedMessages && realPeer;

View File

@ -6,8 +6,7 @@ import type { ApiPeer } from '../../api/types';
import type { CustomPeer } from '../../types'; import type { CustomPeer } from '../../types';
import type { IconName } from '../../types/icons'; import type { IconName } from '../../types/icons';
import { getPeerTitle } from '../../global/helpers'; import { getPeerTitle, isApiPeerChat } from '../../global/helpers/peers';
import { isApiPeerChat } from '../../global/helpers/peers';
import { selectPeer, selectUser } from '../../global/selectors'; import { selectPeer, selectUser } from '../../global/selectors';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { getPeerColorClass } from './helpers/peerColor'; import { getPeerColorClass } from './helpers/peerColor';

View File

@ -14,13 +14,13 @@ import {
getMessageIsSpoiler, getMessageIsSpoiler,
getMessageMediaHash, getMessageMediaHash,
getMessageRoundVideo, getMessageRoundVideo,
getPeerTitle,
isChatChannel, isChatChannel,
isChatGroup, isChatGroup,
isMessageTranslatable, isMessageTranslatable,
isUserId, isUserId,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { getMediaContentTypeDescription } from '../../../global/helpers/messageSummary'; import { getMediaContentTypeDescription } from '../../../global/helpers/messageSummary';
import { getPeerTitle } from '../../../global/helpers/peers';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import freezeWhenClosed from '../../../util/hoc/freezeWhenClosed'; import freezeWhenClosed from '../../../util/hoc/freezeWhenClosed';
import { getPictogramDimensions } from '../helpers/mediaDimensions'; import { getPictogramDimensions } from '../helpers/mediaDimensions';

View File

@ -6,9 +6,9 @@ import type { ApiPeer, ApiTypeStory } from '../../../api/types';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import { import {
getPeerTitle,
getStoryMediaHash, getStoryMediaHash,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { getPeerTitle } from '../../../global/helpers/peers';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { getPictogramDimensions } from '../helpers/mediaDimensions'; import { getPictogramDimensions } from '../helpers/mediaDimensions';
import renderText from '../helpers/renderText'; import renderText from '../helpers/renderText';

View File

@ -10,9 +10,9 @@ import type {
import type { IconName } from '../../../types/icons'; import type { IconName } from '../../../types/icons';
import { import {
getPeerTitle,
isUserId, isUserId,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { getPeerTitle } from '../../../global/helpers/peers';
import { selectPeer, selectPeerStory } from '../../../global/selectors'; import { selectPeer, selectPeerStory } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { getPeerColorClass } from '../helpers/peerColor'; import { getPeerColorClass } from '../helpers/peerColor';

View File

@ -15,10 +15,10 @@ import {
getMessageMediaHash, getMessageMediaHash,
getMessageMediaThumbDataUri, getMessageMediaThumbDataUri,
getMessageRoundVideo, getMessageRoundVideo,
getMessageSenderName,
getMessageSticker, getMessageSticker,
getMessageVideo, getMessageVideo,
} from '../../../../global/helpers'; } from '../../../../global/helpers';
import { getMessageSenderName } from '../../../../global/helpers/peers';
import buildClassName from '../../../../util/buildClassName'; import buildClassName from '../../../../util/buildClassName';
import renderText from '../../../common/helpers/renderText'; import renderText from '../../../common/helpers/renderText';
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities'; import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';

View File

@ -3,10 +3,10 @@ import type { OldLangFn } from '../../../../hooks/useOldLang';
import { import {
getChatTitle, getChatTitle,
getPeerTitle,
isChatGroup, isChatGroup,
isUserId, isUserId,
} from '../../../../global/helpers'; } from '../../../../global/helpers';
import { getPeerTitle } from '../../../../global/helpers/peers';
export function getSenderName( export function getSenderName(
lang: OldLangFn, message: ApiMessage, chatsById: Record<string, ApiChat>, usersById: Record<string, ApiUser>, lang: OldLangFn, message: ApiMessage, chatsById: Record<string, ApiChat>, usersById: Record<string, ApiUser>,

View File

@ -10,3 +10,9 @@
transform: translateY(-50%); transform: translateY(-50%);
color: var(--color-gray); color: var(--color-gray);
} }
.checked .lock-icon {
left: 1.25rem;
font-size: 1rem;
color: var(--color-primary);
}

View File

@ -1,6 +1,8 @@
import React, { memo } from '../../../lib/teact/teact'; import React, { memo } from '../../../lib/teact/teact';
import { getActions } from '../../../global'; import { getActions } from '../../../global';
import buildClassName from '../../../util/buildClassName';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import Icon from '../../common/icons/Icon'; import Icon from '../../common/icons/Icon';
@ -9,15 +11,19 @@ import styles from './PrivacyLockedOption.module.scss';
type OwnProps = { type OwnProps = {
label: string; label: string;
isChecked?: boolean;
}; };
function PrivacyLockedOption({ label }: OwnProps) { function PrivacyLockedOption({ label, isChecked }: OwnProps) {
const lang = useOldLang(); const lang = useOldLang();
const { showNotification } = getActions(); const { showNotification } = getActions();
return ( return (
<div <div
className={styles.root} className={buildClassName(
styles.root,
isChecked && styles.checked,
)}
onClick={() => showNotification({ message: lang('OptionPremiumRequiredMessage') })} onClick={() => showNotification({ message: lang('OptionPremiumRequiredMessage') })}
> >
<span>{label}</span> <span>{label}</span>

View File

@ -72,6 +72,12 @@ function PrivacyMessages({
const canChangeChargeForMessages = isCurrentUserPremium && canChargeForMessages; const canChangeChargeForMessages = isCurrentUserPremium && canChargeForMessages;
const [chargeForMessages, setChargeForMessages] = useState<number>(nonContactPeersPaidStars); const [chargeForMessages, setChargeForMessages] = useState<number>(nonContactPeersPaidStars);
const selectedValue = useMemo(() => {
if (shouldChargeForMessages) return 'charge_for_messages';
if (shouldNewNonContactPeersRequirePremium) return 'contacts_and_premium';
return 'everybody';
}, [shouldChargeForMessages, shouldNewNonContactPeersRequirePremium]);
const options = useMemo(() => { const options = useMemo(() => {
return [ return [
{ value: 'everybody', label: oldLang('P2PEverybody') }, { value: 'everybody', label: oldLang('P2PEverybody') },
@ -80,21 +86,29 @@ function PrivacyMessages({
label: canChangeForContactsAndPremium ? ( label: canChangeForContactsAndPremium ? (
oldLang('PrivacyMessagesContactsAndPremium') oldLang('PrivacyMessagesContactsAndPremium')
) : ( ) : (
<PrivacyLockedOption label={oldLang('PrivacyMessagesContactsAndPremium')} /> <PrivacyLockedOption
label={oldLang('PrivacyMessagesContactsAndPremium')}
isChecked={selectedValue === 'contacts_and_premium'}
/>
), ),
hidden: !canChangeForContactsAndPremium, hidden: !canChangeForContactsAndPremium,
isCanCheckedInDisabled: true,
}, },
{ {
value: 'charge_for_messages', value: 'charge_for_messages',
label: canChangeChargeForMessages ? ( label: canChangeChargeForMessages ? (
lang('PrivacyChargeForMessages') lang('PrivacyChargeForMessages')
) : ( ) : (
<PrivacyLockedOption label={lang('PrivacyChargeForMessages')} /> <PrivacyLockedOption
label={lang('PrivacyChargeForMessages')}
isChecked={selectedValue === 'charge_for_messages'}
/>
), ),
hidden: !canChangeChargeForMessages, hidden: !canChangeChargeForMessages,
isCanCheckedInDisabled: true,
}, },
]; ];
}, [oldLang, lang, canChangeForContactsAndPremium, canChangeChargeForMessages]); }, [oldLang, lang, canChangeForContactsAndPremium, canChangeChargeForMessages, selectedValue]);
const handleChange = useLastCallback((privacy: string) => { const handleChange = useLastCallback((privacy: string) => {
updateGlobalPrivacySettings({ updateGlobalPrivacySettings({
@ -184,12 +198,6 @@ function PrivacyMessages({
onBack: onReset, onBack: onReset,
}); });
const selectedValue = useMemo(() => {
if (shouldChargeForMessages) return 'charge_for_messages';
if (shouldNewNonContactPeersRequirePremium) return 'contacts_and_premium';
return 'everybody';
}, [shouldChargeForMessages, shouldNewNonContactPeersRequirePremium]);
const privacyDescription = useMemo(() => { const privacyDescription = useMemo(() => {
if (shouldChargeForMessages) return lang('PrivacyDescriptionChargeForMessages'); if (shouldChargeForMessages) return lang('PrivacyDescriptionChargeForMessages');
return lang('PrivacyDescriptionMessagesContactsAndPremium'); return lang('PrivacyDescriptionMessagesContactsAndPremium');

View File

@ -6,8 +6,9 @@ import type { ApiChat, ApiPeer } from '../../api/types';
import type { MediaViewerItem } from './helpers/getViewableMedia'; import type { MediaViewerItem } from './helpers/getViewableMedia';
import { import {
getPeerTitle, isChatChannel, isChatGroup, isUserId, isChatChannel, isChatGroup, isUserId,
} from '../../global/helpers'; } from '../../global/helpers';
import { getPeerTitle } from '../../global/helpers/peers';
import { import {
selectSender, selectSender,
} from '../../global/selectors'; } from '../../global/selectors';

View File

@ -14,11 +14,11 @@ import { SCHEDULED_WHEN_ONLINE } from '../../config';
import { import {
getMessageHtmlId, getMessageHtmlId,
getMessageOriginalId, getMessageOriginalId,
getPeerTitle,
isActionMessage, isActionMessage,
isOwnMessage, isOwnMessage,
isServiceNotificationMessage, isServiceNotificationMessage,
} from '../../global/helpers'; } from '../../global/helpers';
import { getPeerTitle } from '../../global/helpers/peers';
import { selectSender } from '../../global/selectors'; import { selectSender } from '../../global/selectors';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { formatHumanDate } from '../../util/dates/dateFormat'; import { formatHumanDate } from '../../util/dates/dateFormat';

View File

@ -6,7 +6,7 @@ import React, {
import type { ApiAttachMenuPeerType, ApiMessage } from '../../../api/types'; import type { ApiAttachMenuPeerType, ApiMessage } from '../../../api/types';
import type { GlobalState } from '../../../global/types'; import type { GlobalState } from '../../../global/types';
import type { ISettings, ThreadId } from '../../../types'; import type { ISettings, MessageListType, ThreadId } from '../../../types';
import { import {
CONTENT_TYPES_WITH_PREVIEW, DEBUG_LOG_FILENAME, SUPPORTED_AUDIO_CONTENT_TYPES, CONTENT_TYPES_WITH_PREVIEW, DEBUG_LOG_FILENAME, SUPPORTED_AUDIO_CONTENT_TYPES,
@ -27,6 +27,7 @@ import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
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 useMouseInside from '../../../hooks/useMouseInside'; import useMouseInside from '../../../hooks/useMouseInside';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
@ -60,6 +61,8 @@ export type OwnProps = {
onMenuClose: NoneToVoidFunction; onMenuClose: NoneToVoidFunction;
canEditMedia?: boolean; canEditMedia?: boolean;
editingMessage?: ApiMessage; editingMessage?: ApiMessage;
messageListType?: MessageListType;
paidMessagesStars?: number;
}; };
const AttachMenu: FC<OwnProps> = ({ const AttachMenu: FC<OwnProps> = ({
@ -83,6 +86,8 @@ const AttachMenu: FC<OwnProps> = ({
onPollCreate, onPollCreate,
canEditMedia, canEditMedia,
editingMessage, editingMessage,
messageListType,
paidMessagesStars,
}) => { }) => {
const [isAttachMenuOpen, openAttachMenu, closeAttachMenu] = useFlag(); const [isAttachMenuOpen, openAttachMenu, closeAttachMenu] = useFlag();
const [handleMouseEnter, handleMouseLeave, markMouseInside] = useMouseInside(isAttachMenuOpen, closeAttachMenu); const [handleMouseEnter, handleMouseLeave, markMouseInside] = useMouseInside(isAttachMenuOpen, closeAttachMenu);
@ -164,7 +169,8 @@ const AttachMenu: FC<OwnProps> = ({
: undefined; : undefined;
}, [attachBots, chatId, peerType]); }, [attachBots, chatId, peerType]);
const lang = useOldLang(); const oldLang = useOldLang();
const lang = useLang();
if (!isButtonVisible) { if (!isButtonVisible) {
return undefined; return undefined;
@ -221,31 +227,35 @@ const AttachMenu: FC<OwnProps> = ({
** transferring to the fragment content in the second clause ** transferring to the fragment content in the second clause
*/} */}
{!canAttachMedia && ( {!canAttachMedia && (
<MenuItem className="media-disabled" disabled>Posting media content is not allowed in this group.</MenuItem> <MenuItem className="media-disabled" disabled>
{lang(messageListType === 'scheduled' && paidMessagesStars
? 'DescriptionScheduledPaidMediaNotAllowed'
: 'DescriptionRestrictedMedia')}
</MenuItem>
)} )}
{canAttachMedia && ( {canAttachMedia && (
<> <>
{canSendVideoOrPhoto && !isFile && ( {canSendVideoOrPhoto && !isFile && (
<MenuItem icon="photo" onClick={handleQuickSelect}> <MenuItem icon="photo" onClick={handleQuickSelect}>
{lang(canSendVideoAndPhoto ? 'AttachmentMenu.PhotoOrVideo' {oldLang(canSendVideoAndPhoto ? 'AttachmentMenu.PhotoOrVideo'
: (canSendPhotos ? 'InputAttach.Popover.Photo' : 'InputAttach.Popover.Video'))} : (canSendPhotos ? 'InputAttach.Popover.Photo' : 'InputAttach.Popover.Video'))}
</MenuItem> </MenuItem>
)} )}
{((canSendDocuments || canSendAudios) && !isPhotoOrVideo) {((canSendDocuments || canSendAudios) && !isPhotoOrVideo)
&& ( && (
<MenuItem icon="document" onClick={handleDocumentSelect}> <MenuItem icon="document" onClick={handleDocumentSelect}>
{lang(!canSendDocuments && canSendAudios ? 'InputAttach.Popover.Music' : 'AttachDocument')} {oldLang(!canSendDocuments && canSendAudios ? 'InputAttach.Popover.Music' : 'AttachDocument')}
</MenuItem> </MenuItem>
)} )}
{canSendDocuments && shouldCollectDebugLogs && ( {canSendDocuments && shouldCollectDebugLogs && (
<MenuItem icon="bug" onClick={handleSendLogs}> <MenuItem icon="bug" onClick={handleSendLogs}>
{lang('DebugSendLogs')} {oldLang('DebugSendLogs')}
</MenuItem> </MenuItem>
)} )}
</> </>
)} )}
{canAttachPolls && !editingMessage && ( {canAttachPolls && !editingMessage && (
<MenuItem icon="poll" onClick={onPollCreate}>{lang('Poll')}</MenuItem> <MenuItem icon="poll" onClick={onPollCreate}>{oldLang('Poll')}</MenuItem>
)} )}
{!editingMessage && !canEditMedia && !isScheduled && bots?.map((bot) => ( {!editingMessage && !canEditMedia && !isScheduled && bots?.map((bot) => (

View File

@ -8,7 +8,9 @@ import React, {
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { ApiInputMessageReplyInfo } from '../../../api/types'; import type { ApiInputMessageReplyInfo } from '../../../api/types';
import type { IAnchorPosition, ISettings, ThreadId } from '../../../types'; import type {
IAnchorPosition, ISettings, MessageListType, ThreadId,
} from '../../../types';
import type { Signal } from '../../../util/signals'; import type { Signal } from '../../../util/signals';
import { EDITABLE_INPUT_ID } from '../../../config'; import { EDITABLE_INPUT_ID } from '../../../config';
@ -75,6 +77,7 @@ type OwnProps = {
onFocus?: NoneToVoidFunction; onFocus?: NoneToVoidFunction;
onBlur?: NoneToVoidFunction; onBlur?: NoneToVoidFunction;
isNeedPremium?: boolean; isNeedPremium?: boolean;
messageListType?: MessageListType;
}; };
type StateProps = { type StateProps = {
@ -141,6 +144,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
onFocus, onFocus,
onBlur, onBlur,
isNeedPremium, isNeedPremium,
messageListType,
}) => { }) => {
const { const {
editLastMessage, editLastMessage,
@ -456,7 +460,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
function handleClick() { function handleClick() {
if (isAttachmentModalInput || canSendPlainText || (isStoryInput && isNeedPremium)) return; if (isAttachmentModalInput || canSendPlainText || (isStoryInput && isNeedPremium)) return;
showAllowedMessageTypesNotification({ chatId }); showAllowedMessageTypesNotification({ chatId, messageListType });
} }
const handleOpenPremiumModal = useLastCallback(() => openPremiumModal()); const handleOpenPremiumModal = useLastCallback(() => openPremiumModal());

View File

@ -1,4 +1,4 @@
import { useRef, useState } from '../../../../lib/teact/teact'; import { useEffect, useRef, useState } from '../../../../lib/teact/teact';
import { getActions, getGlobal } from '../../../../global'; import { getActions, getGlobal } from '../../../../global';
import { PAID_MESSAGES_PURPOSE } from '../../../../config'; import { PAID_MESSAGES_PURPOSE } from '../../../../config';
@ -7,6 +7,8 @@ import useLastCallback from '../../../../hooks/useLastCallback';
export default function usePaidMessageConfirmation( export default function usePaidMessageConfirmation(
starsForAllMessages: number, starsForAllMessages: number,
isStarsBalanceModeOpen: boolean,
starsBalance: number,
) { ) {
const { const {
shouldPaidMessageAutoApprove, shouldPaidMessageAutoApprove,
@ -14,41 +16,63 @@ export default function usePaidMessageConfirmation(
const [shouldAutoApprove, const [shouldAutoApprove,
setAutoApprove] = useState(Boolean(shouldPaidMessageAutoApprove)); setAutoApprove] = useState(Boolean(shouldPaidMessageAutoApprove));
const [isWaitingStarsTopup, setIsWaitingStarsTopup] = useState(false);
const confirmPaymentHandlerRef = useRef<NoneToVoidFunction | undefined>(undefined); const confirmPaymentHandlerRef = useRef<NoneToVoidFunction | undefined>(undefined);
const closeConfirmDialog = useLastCallback(() => { const closeConfirmDialog = useLastCallback(() => {
getActions().closePaymentMessageConfirmDialogOpen(); getActions().closePaymentMessageConfirmDialogOpen();
}); });
useEffect(() => {
if (isWaitingStarsTopup && !isStarsBalanceModeOpen) {
setIsWaitingStarsTopup(false);
if (starsBalance > starsForAllMessages) {
confirmPaymentHandlerRef?.current?.();
}
}
}, [isWaitingStarsTopup, isStarsBalanceModeOpen, starsBalance, starsForAllMessages]);
const handleStarsTopup = useLastCallback(() => {
getActions().openStarsBalanceModal({
topup: {
balanceNeeded: starsForAllMessages,
purpose: PAID_MESSAGES_PURPOSE,
},
});
setIsWaitingStarsTopup(true);
});
const dialogHandler = useLastCallback(() => {
if (starsForAllMessages > starsBalance) {
handleStarsTopup();
} else {
confirmPaymentHandlerRef?.current?.();
}
getActions().closePaymentMessageConfirmDialogOpen();
if (shouldAutoApprove) getActions().setPaidMessageAutoApprove();
});
const handleWithConfirmation = <T extends (...args: any[]) => void>( const handleWithConfirmation = <T extends (...args: any[]) => void>(
handler: T, handler: T,
...args: Parameters<T> ...args: Parameters<T>
) => { ) => {
if (starsForAllMessages) { if (starsForAllMessages) {
const balance = getGlobal().stars?.balance.amount; confirmPaymentHandlerRef.current = () => handler(...args);
if (balance && starsForAllMessages > balance) { if (!shouldPaidMessageAutoApprove) {
getActions().openStarsBalanceModal({ getActions().openPaymentMessageConfirmDialogOpen();
topup: return;
{ balanceNeeded: starsForAllMessages, purpose: PAID_MESSAGES_PURPOSE }, }
});
if (starsForAllMessages > starsBalance) {
handleStarsTopup();
return; return;
} }
} }
if (!shouldPaidMessageAutoApprove && starsForAllMessages) { handler(...args);
confirmPaymentHandlerRef.current = () => handler(...args);
getActions().openPaymentMessageConfirmDialogOpen();
} else {
handler(...args);
}
}; };
const dialogHandler = useLastCallback(() => {
confirmPaymentHandlerRef.current?.();
getActions().closePaymentMessageConfirmDialogOpen();
if (shouldAutoApprove) getActions().setPaidMessageAutoApprove();
});
return { return {
closeConfirmDialog, closeConfirmDialog,
handleWithConfirmation, handleWithConfirmation,

View File

@ -5,8 +5,9 @@ import type { ApiChat, ApiMessage, ApiPeer } from '../../../api/types';
import { GENERAL_TOPIC_ID, SERVICE_NOTIFICATIONS_USER_ID, TME_LINK_PREFIX } from '../../../config'; import { GENERAL_TOPIC_ID, SERVICE_NOTIFICATIONS_USER_ID, TME_LINK_PREFIX } from '../../../config';
import { import {
getMessageInvoice, getMessageText, getPeerTitle, isChatChannel, getMessageInvoice, getMessageText, isChatChannel,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { getPeerTitle } from '../../../global/helpers/peers';
import { getMessageReplyInfo } from '../../../global/helpers/replies'; import { getMessageReplyInfo } from '../../../global/helpers/replies';
import { import {
selectChat, selectChat,

View File

@ -53,7 +53,6 @@ import {
getMessageHtmlId, getMessageHtmlId,
getMessageSingleCustomEmoji, getMessageSingleCustomEmoji,
getMessageSingleRegularEmoji, getMessageSingleRegularEmoji,
getPeerFullTitle,
hasMessageText, hasMessageText,
hasMessageTtl, hasMessageTtl,
isAnonymousForwardsChat, isAnonymousForwardsChat,
@ -69,6 +68,7 @@ import {
isSystemBot, isSystemBot,
isUserId, isUserId,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { getPeerFullTitle } from '../../../global/helpers/peers';
import { getMessageReplyInfo, getStoryReplyInfo } from '../../../global/helpers/replies'; import { getMessageReplyInfo, getStoryReplyInfo } from '../../../global/helpers/replies';
import { import {
selectActiveDownloads, selectActiveDownloads,

View File

@ -5,7 +5,8 @@ import type {
ApiMessage, ApiPeer, ApiTypeStory, ApiUser, ApiMessage, ApiPeer, ApiTypeStory, ApiUser,
} from '../../../api/types'; } from '../../../api/types';
import { getPeerTitle, getStoryMediaHash, getUserFirstOrLastName } from '../../../global/helpers'; import { getStoryMediaHash, getUserFirstOrLastName } from '../../../global/helpers';
import { getPeerTitle } from '../../../global/helpers/peers';
import { import {
selectPeer, selectPeer,
selectPeerStories, selectPeerStories,

View File

@ -4,7 +4,7 @@ import { withGlobal } from '../../../../global';
import type { ApiChat, ApiSticker } from '../../../../api/types'; import type { ApiChat, ApiSticker } from '../../../../api/types';
import type { ApiMessageActionGiftCode, ApiMessageActionPrizeStars } from '../../../../api/types/messageActions'; import type { ApiMessageActionGiftCode, ApiMessageActionPrizeStars } from '../../../../api/types/messageActions';
import { getPeerTitle } from '../../../../global/helpers'; import { getPeerTitle } from '../../../../global/helpers/peers';
import { import {
selectCanPlayAnimatedEmojis, selectCanPlayAnimatedEmojis,
selectChat, selectChat,

View File

@ -4,8 +4,8 @@ import { withGlobal } from '../../../../global';
import type { ApiMessage, ApiPeer } from '../../../../api/types'; import type { ApiMessage, ApiPeer } from '../../../../api/types';
import type { ApiMessageActionStarGift } from '../../../../api/types/messageActions'; import type { ApiMessageActionStarGift } from '../../../../api/types/messageActions';
import { getPeerTitle, isChatChannel } from '../../../../global/helpers'; import { isChatChannel } from '../../../../global/helpers';
import { isApiPeerChat } from '../../../../global/helpers/peers'; import { getPeerTitle, isApiPeerChat } from '../../../../global/helpers/peers';
import { import {
selectCanPlayAnimatedEmojis, selectCanPlayAnimatedEmojis,
selectPeer, selectPeer,

View File

@ -4,7 +4,7 @@ import { withGlobal } from '../../../../global';
import type { ApiMessage, ApiPeer } from '../../../../api/types'; import type { ApiMessage, ApiPeer } from '../../../../api/types';
import type { ApiMessageActionStarGiftUnique } from '../../../../api/types/messageActions'; import type { ApiMessageActionStarGiftUnique } from '../../../../api/types/messageActions';
import { getPeerTitle } from '../../../../global/helpers'; import { getPeerTitle } from '../../../../global/helpers/peers';
import { import {
selectCanPlayAnimatedEmojis, selectCanPlayAnimatedEmojis,
selectPeer, selectPeer,

View File

@ -5,7 +5,8 @@ import type { ApiMessageActionSuggestProfilePhoto } from '../../../../api/types/
import { type ApiMessage, type ApiPeer, MAIN_THREAD_ID } from '../../../../api/types'; import { type ApiMessage, type ApiPeer, MAIN_THREAD_ID } from '../../../../api/types';
import { MediaViewerOrigin, SettingsScreens } from '../../../../types'; import { MediaViewerOrigin, SettingsScreens } from '../../../../types';
import { getPeerTitle, getPhotoMediaHash, getVideoProfilePhotoMediaHash } from '../../../../global/helpers'; import { getPhotoMediaHash, getVideoProfilePhotoMediaHash } from '../../../../global/helpers';
import { getPeerTitle } from '../../../../global/helpers/peers';
import { selectPeer } from '../../../../global/selectors'; import { selectPeer } from '../../../../global/selectors';
import { fetchBlob } from '../../../../util/files'; import { fetchBlob } from '../../../../util/files';
import { renderPeerLink } from '../helpers/messageActions'; import { renderPeerLink } from '../helpers/messageActions';

View File

@ -10,8 +10,9 @@ import type { IconName } from '../../../types/icons';
import { PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION } from '../../../config'; import { PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION } from '../../../config';
import { import {
getMediaDuration, getMessageContent, getMessageMediaHash, getPeerTitle, isMessageLocal, getMediaDuration, getMessageContent, getMessageMediaHash, isMessageLocal,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { getPeerTitle } from '../../../global/helpers/peers';
import { import {
selectChat, selectChatMessage, selectSender, selectTabState, selectChat, selectChatMessage, selectSender, selectTabState,
} from '../../../global/selectors'; } from '../../../global/selectors';

View File

@ -12,8 +12,8 @@ import {
getMessageMediaHash, getMessageMediaHash,
getMessageSingleInlineButton, getMessageSingleInlineButton,
getMessageVideo, getMessageVideo,
getPeerTitle,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { getPeerTitle } from '../../../global/helpers/peers';
import { import {
selectAllowedMessageActionsSlow, selectAllowedMessageActionsSlow,
selectChat, selectChat,

View File

@ -33,9 +33,3 @@
margin-inline-start: 0 !important; margin-inline-start: 0 !important;
margin-inline-end: 0.125rem !important; margin-inline-end: 0.125rem !important;
} }
.checkBox {
margin-top: 0.375rem;
margin-inline: -1.125rem;
padding-inline-start: 3.5rem;
}

View File

@ -7,23 +7,20 @@ import type {
} from '../../../api/types'; } from '../../../api/types';
import { import {
getPeerTitle,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { getPeerTitle } from '../../../global/helpers/peers';
import { import {
selectChat, selectChat,
selectUserFullInfo, selectUserFullInfo,
} from '../../../global/selectors'; } from '../../../global/selectors';
import { formatStarsAsIcon } from '../../../util/localization/format'; import { formatStarsAsIcon } from '../../../util/localization/format';
import useFlag from '../../../hooks/useFlag';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
// import useTimeout from '../../../hooks/schedulers/useTimeout'; // import useTimeout from '../../../hooks/schedulers/useTimeout';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useHeaderPane, { type PaneState } from '../hooks/useHeaderPane'; import useHeaderPane, { type PaneState } from '../hooks/useHeaderPane';
import Button from '../../ui/Button'; import Button from '../../ui/Button';
import Checkbox from '../../ui/Checkbox';
import ConfirmDialog from '../../ui/ConfirmDialog';
// import CustomEmoji from '../../common/CustomEmoji'; // import CustomEmoji from '../../common/CustomEmoji';
import styles from './PaidMessageChargePane.module.scss'; import styles from './PaidMessageChargePane.module.scss';
@ -41,16 +38,14 @@ type StateProps = {
const PaidMessageChargePane: FC<OwnProps & StateProps> = ({ const PaidMessageChargePane: FC<OwnProps & StateProps> = ({
chargedPaidMessageStars, chargedPaidMessageStars,
chat, chat,
onPaneStateChange,
peerId, peerId,
onPaneStateChange,
}) => { }) => {
const isOpen = Boolean(chargedPaidMessageStars); const isOpen = Boolean(chargedPaidMessageStars);
const lang = useLang(); const lang = useLang();
const [isRemoveFeeDialogOpen, openRemoveFeeDialog, closeRemoveFeeDialog] = useFlag();
const [shouldRefoundStars, setShouldRefoundStars] = useFlag(false);
const { const {
addNoPaidMessagesException, openChatRefundModal,
} = getActions(); } = getActions();
const { ref, shouldRender } = useHeaderPane({ const { ref, shouldRender } = useHeaderPane({
@ -58,12 +53,8 @@ const PaidMessageChargePane: FC<OwnProps & StateProps> = ({
onStateChange: onPaneStateChange, onStateChange: onPaneStateChange,
}); });
const handleRemoveFee = useLastCallback(() => { const handleRefund = useLastCallback(() => {
openRemoveFeeDialog(); openChatRefundModal({ userId: peerId });
});
const handleConfirmRemoveFee = useLastCallback(() => {
addNoPaidMessagesException({ userId: peerId, shouldRefundCharged: shouldRefoundStars });
}); });
if (!shouldRender || !chargedPaidMessageStars) return undefined; if (!shouldRender || !chargedPaidMessageStars) return undefined;
@ -80,20 +71,6 @@ const PaidMessageChargePane: FC<OwnProps & StateProps> = ({
withNodes: true, withNodes: true,
}); });
const dialogMessage = lang('ConfirmDialogMessageRemoveFee', {
peer: peerName,
}, {
withMarkdown: true,
withNodes: true,
});
const checkBoxTitle = lang('ConfirmDialogRemoveFeeRefundStars', {
amount: chargedPaidMessageStars,
}, {
withMarkdown: true,
withNodes: true,
});
return ( return (
<div ref={ref} className={styles.root}> <div ref={ref} className={styles.root}>
<div className={styles.message}> <div className={styles.message}>
@ -106,26 +83,10 @@ const PaidMessageChargePane: FC<OwnProps & StateProps> = ({
fluid fluid
size="tiny" size="tiny"
className={styles.button} className={styles.button}
onClick={handleRemoveFee} onClick={handleRefund}
> >
{lang('RemoveFeeTitle')} {lang('RemoveFeeTitle')}
</Button> </Button>
<ConfirmDialog
isOpen={isRemoveFeeDialogOpen}
onClose={closeRemoveFeeDialog}
title={lang('RemoveFeeTitle')}
confirmLabel={lang('ConfirmRemoveMessageFee')}
confirmHandler={handleConfirmRemoveFee}
>
{dialogMessage}
<Checkbox
className={styles.checkBox}
label={checkBoxTitle}
checked={shouldRefoundStars}
onCheck={setShouldRefoundStars}
/>
</ConfirmDialog>
</div> </div>
); );
}; };

View File

@ -2,7 +2,7 @@ import React, { memo } from '../../../lib/teact/teact';
import type { ApiChat, ApiMessage, ApiPeer } from '../../../api/types'; import type { ApiChat, ApiMessage, ApiPeer } from '../../../api/types';
import { getMessageSenderName } from '../../../global/helpers'; import { getMessageSenderName } from '../../../global/helpers/peers';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';

View File

@ -32,6 +32,7 @@ import PreparedMessageModal from './preparedMessage/PreparedMessageModal.async';
import ReportAdModal from './reportAd/ReportAdModal.async'; import ReportAdModal from './reportAd/ReportAdModal.async';
import ReportModal from './reportModal/ReportModal.async'; import ReportModal from './reportModal/ReportModal.async';
import SharePreparedMessageModal from './sharePreparedMessage/SharePreparedMessageModal.async'; import SharePreparedMessageModal from './sharePreparedMessage/SharePreparedMessageModal.async';
import ChatRefundModal from './stars/chatRefund/ChatRefundModal.async';
import StarsGiftModal from './stars/gift/StarsGiftModal.async'; import StarsGiftModal from './stars/gift/StarsGiftModal.async';
import StarsBalanceModal from './stars/StarsBalanceModal.async'; import StarsBalanceModal from './stars/StarsBalanceModal.async';
import StarsPaymentModal from './stars/StarsPaymentModal.async'; import StarsPaymentModal from './stars/StarsPaymentModal.async';
@ -77,7 +78,8 @@ type ModalKey = keyof Pick<TabState,
'preparedMessageModal' | 'preparedMessageModal' |
'sharePreparedMessageModal' | 'sharePreparedMessageModal' |
'giftStatusInfoModal' | 'giftStatusInfoModal' |
'giftTransferModal' 'giftTransferModal' |
'chatRefundModal'
>; >;
type StateProps = { type StateProps = {
@ -127,6 +129,7 @@ const MODALS: ModalRegistry = {
preparedMessageModal: PreparedMessageModal, preparedMessageModal: PreparedMessageModal,
sharePreparedMessageModal: SharePreparedMessageModal, sharePreparedMessageModal: SharePreparedMessageModal,
giftTransferModal: GiftTransferModal, giftTransferModal: GiftTransferModal,
chatRefundModal: ChatRefundModal,
}; };
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[]; const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>; const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;

View File

@ -11,9 +11,8 @@ import {
} from '../../../api/types'; } from '../../../api/types';
import { import {
getPeerTitle,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { isApiPeerUser } from '../../../global/helpers/peers'; import { getPeerTitle, isApiPeerUser } from '../../../global/helpers/peers';
import { import {
selectPeer, selectPeerPaidMessagesStars, selectPeer, selectPeerPaidMessagesStars,
selectTabState, selectTheme, selectTabState, selectTheme,

View File

@ -14,8 +14,8 @@ import type { TabState } from '../../../global/types';
import type { StarGiftCategory } from '../../../types'; import type { StarGiftCategory } from '../../../types';
import { STARS_CURRENCY_CODE } from '../../../config'; import { STARS_CURRENCY_CODE } from '../../../config';
import { getPeerTitle, getUserFullName } from '../../../global/helpers'; import { getUserFullName } from '../../../global/helpers';
import { isApiPeerChat, isApiPeerUser } from '../../../global/helpers/peers'; import { getPeerTitle, isApiPeerChat, isApiPeerUser } from '../../../global/helpers/peers';
import { selectPeer } from '../../../global/selectors'; import { selectPeer } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';

View File

@ -8,8 +8,8 @@ import type {
} from '../../../../api/types'; } from '../../../../api/types';
import type { TabState } from '../../../../global/types'; import type { TabState } from '../../../../global/types';
import { getHasAdminRight, getPeerTitle } from '../../../../global/helpers'; import { getHasAdminRight } from '../../../../global/helpers';
import { isApiPeerChat } from '../../../../global/helpers/peers'; import { getPeerTitle, isApiPeerChat } from '../../../../global/helpers/peers';
import { selectPeer, selectUser } from '../../../../global/selectors'; import { selectPeer, selectUser } from '../../../../global/selectors';
import buildClassName from '../../../../util/buildClassName'; import buildClassName from '../../../../util/buildClassName';
import { copyTextToClipboard } from '../../../../util/clipboard'; import { copyTextToClipboard } from '../../../../util/clipboard';

View File

@ -8,7 +8,7 @@ import type { TabState } from '../../../../global/types';
import type { UniqueCustomPeer } from '../../../../types'; import type { UniqueCustomPeer } from '../../../../types';
import { ALL_FOLDER_ID } from '../../../../config'; import { ALL_FOLDER_ID } from '../../../../config';
import { getPeerTitle } from '../../../../global/helpers'; import { getPeerTitle } from '../../../../global/helpers/peers';
import { selectCanGift, selectPeer } from '../../../../global/selectors'; import { selectCanGift, selectPeer } from '../../../../global/selectors';
import { unique } from '../../../../util/iteratees'; import { unique } from '../../../../util/iteratees';
import { formatStarsAsIcon, formatStarsAsText } from '../../../../util/localization/format'; import { formatStarsAsIcon, formatStarsAsText } from '../../../../util/localization/format';

View File

@ -14,7 +14,8 @@ import type {
import type { TabState } from '../../../../global/types'; import type { TabState } from '../../../../global/types';
import { ApiMediaFormat } from '../../../../api/types'; import { ApiMediaFormat } from '../../../../api/types';
import { getPeerTitle, getStickerMediaHash } from '../../../../global/helpers'; import { getStickerMediaHash } from '../../../../global/helpers';
import { getPeerTitle } from '../../../../global/helpers/peers';
import { selectPeer } from '../../../../global/selectors'; import { selectPeer } from '../../../../global/selectors';
import { formatStarsAsIcon } from '../../../../util/localization/format'; import { formatStarsAsIcon } from '../../../../util/localization/format';
import { fetch } from '../../../../util/mediaLoader'; import { fetch } from '../../../../util/mediaLoader';

View File

@ -14,8 +14,7 @@ import type { TabState } from '../../../global/types';
import type { CustomPeer } from '../../../types'; import type { CustomPeer } from '../../../types';
import { STARS_ICON_PLACEHOLDER } from '../../../config'; import { STARS_ICON_PLACEHOLDER } from '../../../config';
import { getPeerTitle } from '../../../global/helpers'; import { getPeerTitle, isApiPeerUser } from '../../../global/helpers/peers';
import { isApiPeerUser } from '../../../global/helpers/peers';
import { import {
selectChat, selectChatMessage, selectPeer, selectUser, selectChat, selectChatMessage, selectPeer, selectUser,
} from '../../../global/selectors'; } from '../../../global/selectors';

View File

@ -11,8 +11,8 @@ import type { ThreadId } from '../../../types';
import { MAIN_THREAD_ID } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types';
import { import {
getPeerTitle,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { getPeerTitle } from '../../../global/helpers/peers';
import { import {
selectPeer, selectTabState, selectPeer, selectTabState,
} from '../../../global/selectors'; } from '../../../global/selectors';
@ -31,6 +31,8 @@ export type OwnProps = {
type StateProps = { type StateProps = {
isPaymentMessageConfirmDialogOpen: boolean; isPaymentMessageConfirmDialogOpen: boolean;
starsBalance: number;
isStarsBalanceModalOpen: boolean;
}; };
export type SendParams = { export type SendParams = {
@ -39,7 +41,7 @@ export type SendParams = {
}; };
const SharePreparedMessageModal: FC<OwnProps & StateProps> = ({ const SharePreparedMessageModal: FC<OwnProps & StateProps> = ({
modal, isPaymentMessageConfirmDialogOpen, modal, isPaymentMessageConfirmDialogOpen, isStarsBalanceModalOpen, starsBalance,
}) => { }) => {
const { const {
closeSharePreparedMessageModal, closeSharePreparedMessageModal,
@ -73,7 +75,7 @@ const SharePreparedMessageModal: FC<OwnProps & StateProps> = ({
shouldAutoApprove: shouldPaidMessageAutoApprove, shouldAutoApprove: shouldPaidMessageAutoApprove,
setAutoApprove: setShouldPaidMessageAutoApprove, setAutoApprove: setShouldPaidMessageAutoApprove,
handleWithConfirmation: handleActionWithPaymentConfirmation, handleWithConfirmation: handleActionWithPaymentConfirmation,
} = usePaidMessageConfirmation(starsForSendMessage || 0); } = usePaidMessageConfirmation(starsForSendMessage || 0, isStarsBalanceModalOpen, starsBalance);
const handleClose = useLastCallback(() => { const handleClose = useLastCallback(() => {
closeSharePreparedMessageModal(); closeSharePreparedMessageModal();
@ -118,7 +120,7 @@ const SharePreparedMessageModal: FC<OwnProps & StateProps> = ({
updateSharePreparedMessageModalSendArgs({ args: { peerId: id, threadId } }); updateSharePreparedMessageModalSendArgs({ args: { peerId: id, threadId } });
}); });
const handleSendWithPaymentConformation = useLastCallback(() => { const handleSendWithPaymentConfirmation = useLastCallback(() => {
if (pendingSendArgs) { if (pendingSendArgs) {
handleActionWithPaymentConfirmation(handleSend, pendingSendArgs.peerId, pendingSendArgs.threadId); handleActionWithPaymentConfirmation(handleSend, pendingSendArgs.peerId, pendingSendArgs.threadId);
} }
@ -131,7 +133,7 @@ const SharePreparedMessageModal: FC<OwnProps & StateProps> = ({
useEffect(() => { useEffect(() => {
if (pendingSendArgs) { if (pendingSendArgs) {
handleSendWithPaymentConformation(); handleSendWithPaymentConfirmation();
} }
}, [pendingSendArgs]); }, [pendingSendArgs]);
@ -172,8 +174,12 @@ export default memo(withGlobal(
(global): StateProps => { (global): StateProps => {
const tabState = selectTabState(global); const tabState = selectTabState(global);
const { isPaymentMessageConfirmDialogOpen } = tabState; const { isPaymentMessageConfirmDialogOpen } = tabState;
const starsBalance = global.stars?.balance.amount || 0;
const isStarsBalanceModalOpen = Boolean(tabState.starsBalanceModal);
return { return {
isPaymentMessageConfirmDialogOpen, isPaymentMessageConfirmDialogOpen,
starsBalance,
isStarsBalanceModalOpen,
}; };
}, },
)(SharePreparedMessageModal)); )(SharePreparedMessageModal));

View File

@ -8,7 +8,8 @@ import type { GlobalState, TabState } from '../../../global/types';
import type { RegularLangKey } from '../../../types/language'; import type { RegularLangKey } from '../../../types/language';
import { PAID_MESSAGES_PURPOSE } from '../../../config'; import { PAID_MESSAGES_PURPOSE } from '../../../config';
import { getChatTitle, getPeerTitle, getUserFullName } from '../../../global/helpers'; import { getChatTitle, getUserFullName } from '../../../global/helpers';
import { getPeerTitle } from '../../../global/helpers/peers';
import { selectChat, selectIsPremiumPurchaseBlocked, selectUser } from '../../../global/selectors'; import { selectChat, selectIsPremiumPurchaseBlocked, selectUser } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';

View File

@ -0,0 +1,18 @@
import type { FC } from '../../../../lib/teact/teact';
import React from '../../../../lib/teact/teact';
import type { OwnProps } from './ChatRefundModal';
import { Bundles } from '../../../../util/moduleLoader';
import useModuleLoader from '../../../../hooks/useModuleLoader';
const ChatRefundModalAsync: FC<OwnProps> = (props) => {
const { modal } = props;
const ChatRefundModal = useModuleLoader(Bundles.Stars, 'ChatRefundModal', !modal);
// eslint-disable-next-line react/jsx-props-no-spreading
return ChatRefundModal ? <ChatRefundModal {...props} /> : undefined;
};
export default ChatRefundModalAsync;

View File

@ -0,0 +1,84 @@
import React, { memo, useState } from '../../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../../global';
import type { ApiUser } from '../../../../api/types';
import type { TabState } from '../../../../global/types';
import { getPeerTitle } from '../../../../global/helpers/peers';
import { selectUser } from '../../../../global/selectors';
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
import useLang from '../../../../hooks/useLang';
import useLastCallback from '../../../../hooks/useLastCallback';
import Checkbox from '../../../ui/Checkbox';
import ConfirmDialog from '../../../ui/ConfirmDialog';
export type OwnProps = {
modal: TabState['chatRefundModal'];
};
type StateProps = {
user?: ApiUser;
};
const ChatRefundModal = ({ modal, user }: OwnProps & StateProps) => {
const { closeChatRefundModal, addNoPaidMessagesException } = getActions();
const [shouldRefundStars, setShouldRefundStars] = useState(false);
const renderingModal = useCurrentOrPrev(modal);
const renderingUser = useCurrentOrPrev(user);
const { starsToRefund, userId } = renderingModal || {};
const lang = useLang();
const isOpen = Boolean(modal);
const handleConfirmRemoveFee = useLastCallback(() => {
closeChatRefundModal();
if (!userId) return;
addNoPaidMessagesException({ userId, shouldRefundCharged: shouldRefundStars });
});
return (
<ConfirmDialog
isOpen={isOpen}
onClose={closeChatRefundModal}
title={lang('RemoveFeeTitle')}
confirmLabel={lang('ConfirmRemoveMessageFee')}
confirmHandler={handleConfirmRemoveFee}
>
{lang('ConfirmDialogMessageRemoveFee', {
peer: renderingUser && getPeerTitle(lang, renderingUser),
}, {
withMarkdown: true,
withNodes: true,
})}
{
Boolean(starsToRefund) && (
<Checkbox
className="dialog-checkbox"
label={lang('ConfirmDialogRemoveFeeRefundStars', {
amount: starsToRefund,
}, {
withMarkdown: true,
withNodes: true,
})}
checked={shouldRefundStars}
onCheck={setShouldRefundStars}
/>
)
}
</ConfirmDialog>
);
};
export default memo(withGlobal<OwnProps>((global, { modal }): StateProps => {
const user = modal?.userId ? selectUser(global, modal.userId) : undefined;
return {
user,
};
})(ChatRefundModal));

View File

@ -3,14 +3,14 @@ import React, {
memo, useEffect, useMemo, useRef, memo, useEffect, useMemo, useRef,
useState, useState,
} from '../../../../lib/teact/teact'; } from '../../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../../global'; import { getActions, withGlobal } from '../../../../global';
import type { import type {
ApiStarTopupOption, ApiUser, ApiStarTopupOption, ApiUser,
} from '../../../../api/types'; } from '../../../../api/types';
import type { TabState } from '../../../../global/types'; import type { TabState } from '../../../../global/types';
import { getPeerTitle } from '../../../../global/helpers'; import { getPeerTitle } from '../../../../global/helpers/peers';
import { import {
selectUser, selectUser,
} from '../../../../global/selectors'; } from '../../../../global/selectors';
@ -213,7 +213,7 @@ const StarsGiftModal: FC<OwnProps & StateProps> = ({
}; };
export default memo(withGlobal<OwnProps>((global, { modal }): StateProps => { export default memo(withGlobal<OwnProps>((global, { modal }): StateProps => {
const user = modal?.forUserId ? selectUser(getGlobal(), modal.forUserId) : undefined; const user = modal?.forUserId ? selectUser(global, modal.forUserId) : undefined;
return { return {
user, user,

View File

@ -6,7 +6,7 @@ import type {
} from '../../../../api/types'; } from '../../../../api/types';
import type { GlobalState } from '../../../../global/types'; import type { GlobalState } from '../../../../global/types';
import { getPeerTitle } from '../../../../global/helpers'; import { getPeerTitle } from '../../../../global/helpers/peers';
import { selectPeer } from '../../../../global/selectors'; import { selectPeer } from '../../../../global/selectors';
import { formatDateToString } from '../../../../util/dates/dateFormat'; import { formatDateToString } from '../../../../util/dates/dateFormat';
import { formatInteger } from '../../../../util/textFormat'; import { formatInteger } from '../../../../util/textFormat';

View File

@ -8,8 +8,8 @@ import type {
import type { GlobalState } from '../../../../global/types'; import type { GlobalState } from '../../../../global/types';
import type { CustomPeer } from '../../../../types'; import type { CustomPeer } from '../../../../types';
import { getPeerTitle } from '../../../../global/helpers';
import { buildStarsTransactionCustomPeer, formatStarsTransactionAmount } from '../../../../global/helpers/payments'; import { buildStarsTransactionCustomPeer, formatStarsTransactionAmount } from '../../../../global/helpers/payments';
import { getPeerTitle } from '../../../../global/helpers/peers';
import { selectPeer } from '../../../../global/selectors'; import { selectPeer } from '../../../../global/selectors';
import buildClassName from '../../../../util/buildClassName'; import buildClassName from '../../../../util/buildClassName';
import { formatDateTimeToString } from '../../../../util/dates/dateFormat'; import { formatDateTimeToString } from '../../../../util/dates/dateFormat';

View File

@ -15,7 +15,8 @@ import type { Signal } from '../../util/signals';
import { MAIN_THREAD_ID } from '../../api/types'; import { MAIN_THREAD_ID } from '../../api/types';
import { EDITABLE_STORY_INPUT_CSS_SELECTOR, EDITABLE_STORY_INPUT_ID } from '../../config'; import { EDITABLE_STORY_INPUT_CSS_SELECTOR, EDITABLE_STORY_INPUT_ID } from '../../config';
import { getPeerTitle, isChatChannel, isUserId } from '../../global/helpers'; import { isChatChannel, isUserId } from '../../global/helpers';
import { getPeerTitle } from '../../global/helpers/peers';
import { import {
selectChat, selectChat,
selectIsCurrentUserPremium, selectIsCurrentUserPremium,

View File

@ -6,7 +6,8 @@ import type {
} from '../../api/types'; } from '../../api/types';
import type { StoryViewerOrigin } from '../../types'; import type { StoryViewerOrigin } from '../../types';
import { getPeerTitle, getStoryMediaHash } from '../../global/helpers'; import { getStoryMediaHash } from '../../global/helpers';
import { getPeerTitle } from '../../global/helpers/peers';
import { selectTabState } from '../../global/selectors'; import { selectTabState } from '../../global/selectors';
import renderText from '../common/helpers/renderText'; import renderText from '../common/helpers/renderText';

View File

@ -4,7 +4,8 @@ import { getActions } from '../../global';
import type { ApiPeer } from '../../api/types'; import type { ApiPeer } from '../../api/types';
import { StoryViewerOrigin } from '../../types'; import { StoryViewerOrigin } from '../../types';
import { getPeerTitle, isUserId } from '../../global/helpers'; import { isUserId } from '../../global/helpers';
import { getPeerTitle } from '../../global/helpers/peers';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur'; import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur';

View File

@ -8,7 +8,8 @@ import type {
} from '../../api/types'; } from '../../api/types';
import type { IconName } from '../../types/icons'; import type { IconName } from '../../types/icons';
import { getPeerTitle, getUserFullName } from '../../global/helpers'; import { getUserFullName } from '../../global/helpers';
import { getPeerTitle } from '../../global/helpers/peers';
import { selectPeerStory, selectTabState } from '../../global/selectors'; import { selectPeerStory, selectTabState } from '../../global/selectors';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { getHours } from '../../util/dates/units'; import { getHours } from '../../util/dates/units';

View File

@ -11,6 +11,8 @@
} }
.Notification { .Notification {
--color-toast-action: var(--color-primary);
background-color: rgba(32, 32, 32, 0.8); background-color: rgba(32, 32, 32, 0.8);
background-size: 1.5rem; background-size: 1.5rem;
border-radius: var(--border-radius-default); border-radius: var(--border-radius-default);

View File

@ -54,6 +54,14 @@
} }
} }
&.canCheckedInDisabled {
.Radio-main {
&::before {
visibility: visible;
}
}
}
> input { > input {
position: absolute; position: absolute;
z-index: var(--z-below); z-index: var(--z-below);

View File

@ -18,6 +18,7 @@ type OwnProps = {
value?: string; value?: string;
checked?: boolean; checked?: boolean;
disabled?: boolean; disabled?: boolean;
isCanCheckedInDisabled?: boolean;
isLink?: boolean; isLink?: boolean;
hidden?: boolean; hidden?: boolean;
isLoading?: boolean; isLoading?: boolean;
@ -46,6 +47,7 @@ const Radio: FC<OwnProps> = ({
isLink, isLink,
onChange, onChange,
onSubLabelClick, onSubLabelClick,
isCanCheckedInDisabled,
}) => { }) => {
const lang = useOldLang(); const lang = useOldLang();
@ -58,6 +60,7 @@ const Radio: FC<OwnProps> = ({
isLoading && 'loading', isLoading && 'loading',
onlyInput && 'onlyInput', onlyInput && 'onlyInput',
Boolean(subLabel) && 'withSubLabel', Boolean(subLabel) && 'withSubLabel',
isCanCheckedInDisabled && 'canCheckedInDisabled',
); );
return ( return (

View File

@ -14,6 +14,7 @@ export type IRadioOption<T = string> = {
value: T; value: T;
hidden?: boolean; hidden?: boolean;
className?: string; className?: string;
isCanCheckedInDisabled?: boolean;
}; };
type OwnProps = { type OwnProps = {
@ -67,6 +68,7 @@ const RadioGroup: FC<OwnProps> = ({
value={option.value} value={option.value}
checked={option.value === selected} checked={option.value === selected}
hidden={option.hidden} hidden={option.hidden}
isCanCheckedInDisabled={option.isCanCheckedInDisabled}
disabled={disabled} disabled={disabled}
withIcon={withIcon} withIcon={withIcon}
isLoading={loadingOption ? loadingOption === option.value : undefined} isLoading={loadingOption ? loadingOption === option.value : undefined}

View File

@ -416,7 +416,7 @@ addActionHandler('sendInlineBotResult', async (global, actions, payload): Promis
actions.resetDraftReplyInfo({ tabId }); actions.resetDraftReplyInfo({ tabId });
actions.clearWebPagePreview({ tabId }); actions.clearWebPagePreview({ tabId });
const starsForOneMessage = await getPeerStarsForMessage(global, chat); const starsForOneMessage = await getPeerStarsForMessage(global, chatId);
const params = { const params = {
chat, chat,
id, id,

View File

@ -8,7 +8,6 @@ import type {
ApiInputStoryReplyInfo, ApiInputStoryReplyInfo,
ApiMessage, ApiMessage,
ApiOnProgress, ApiOnProgress,
ApiPeer,
ApiStory, ApiStory,
ApiUser, ApiUser,
} from '../../../api/types'; } from '../../../api/types';
@ -61,12 +60,11 @@ import {
isChatSuperGroup, isChatSuperGroup,
isDeletedUser, isDeletedUser,
isMessageLocal, isMessageLocal,
isPeerUser,
isServiceNotificationMessage, isServiceNotificationMessage,
isUserBot, isUserBot,
splitMessagesForForwarding, splitMessagesForForwarding,
} from '../../helpers'; } from '../../helpers';
import { isApiPeerUser } from '../../helpers/peers'; import { isApiPeerChat, isApiPeerUser } from '../../helpers/peers';
import { import {
addActionHandler, getActions, getGlobal, setGlobal, addActionHandler, getActions, getGlobal, setGlobal,
} from '../../index'; } from '../../index';
@ -345,7 +343,7 @@ addActionHandler('sendMessage', async (global, actions, payload): Promise<void>
const replyInfo = storyReplyInfo || messageReplyInfo; const replyInfo = storyReplyInfo || messageReplyInfo;
const lastMessageId = selectChatLastMessageId(global, chatId!); const lastMessageId = selectChatLastMessageId(global, chatId!);
const messagePriceInStars = await getPeerStarsForMessage(global, chat); const messagePriceInStars = await getPeerStarsForMessage(global, chatId!);
const params : SendMessageParams = { const params : SendMessageParams = {
...payload, ...payload,
@ -1607,9 +1605,12 @@ function getViewportSlice(
export async function getPeerStarsForMessage<T extends GlobalState>( export async function getPeerStarsForMessage<T extends GlobalState>(
global: T, global: T,
peer: ApiPeer, peerId: string,
): Promise<number | undefined> { ): Promise<number | undefined> {
if (!isPeerUser(peer)) { const peer = selectPeer(global, peerId);
if (!peer) return undefined;
if (isApiPeerChat(peer)) {
return peer.paidMessagesStars; return peer.paidMessagesStars;
} }
@ -1617,7 +1618,7 @@ export async function getPeerStarsForMessage<T extends GlobalState>(
const fullInfo = selectUserFullInfo(global, peer.id); const fullInfo = selectUserFullInfo(global, peer.id);
if (fullInfo) { if (fullInfo) {
return fullInfo?.paidMessagesStars; return fullInfo.paidMessagesStars;
} }
const result = await callApi('fetchPaidMessagesStarsAmount', peer); const result = await callApi('fetchPaidMessagesStarsAmount', peer);
@ -1675,7 +1676,7 @@ async function sendMessagesWithNotification<T extends GlobalState>(
) { ) {
const chat = sendParams[0]?.chat; const chat = sendParams[0]?.chat;
if (!chat || !sendParams.length) return; if (!chat || !sendParams.length) return;
const starsForOneMessage = await getPeerStarsForMessage(global, chat); const starsForOneMessage = await getPeerStarsForMessage(global, chat.id);
if (!starsForOneMessage) { if (!starsForOneMessage) {
// eslint-disable-next-line eslint-multitab-tt/no-getactions-in-actions // eslint-disable-next-line eslint-multitab-tt/no-getactions-in-actions
getActions().sendMessages({ sendParams }); getActions().sendMessages({ sendParams });

View File

@ -204,6 +204,27 @@ addActionHandler('addNoPaidMessagesException', async (global, actions, payload):
setGlobal(global); setGlobal(global);
}); });
addActionHandler('openChatRefundModal', async (global, actions, payload): Promise<void> => {
const { userId, tabId = getCurrentTabId() } = payload;
const user = selectUser(global, userId);
if (!user) {
return;
}
const starsAmount = await callApi('fetchPaidMessagesRevenue', { user });
if (starsAmount === undefined) return;
global = getGlobal();
global = updateTabState(global, {
chatRefundModal: {
userId,
starsToRefund: starsAmount,
},
}, tabId);
setGlobal(global);
});
addActionHandler('updateContact', async (global, actions, payload): Promise<void> => { addActionHandler('updateContact', async (global, actions, payload): Promise<void> => {
const { const {
userId, isMuted = false, firstName, lastName, shouldSharePhoneNumber, userId, isMuted = false, firstName, lastName, shouldSharePhoneNumber,

View File

@ -3,7 +3,7 @@ import { PaymentStep } from '../../../types';
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config'; import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
import { applyLangPackDifference, getTranslationFn, requestLangPackDifference } from '../../../util/localization'; import { applyLangPackDifference, getTranslationFn, requestLangPackDifference } from '../../../util/localization';
import { getPeerTitle } from '../../helpers'; import { getPeerTitle } from '../../helpers/peers';
import { addActionHandler, setGlobal } from '../../index'; import { addActionHandler, setGlobal } from '../../index';
import { import {
addBlockedUser, addBlockedUser,

View File

@ -40,7 +40,6 @@ addActionHandler('processOpenChatOrThread', (global, actions, payload): ActionRe
actions.closeStoryViewer({ tabId }); actions.closeStoryViewer({ tabId });
actions.closeStarsBalanceModal({ tabId }); actions.closeStarsBalanceModal({ tabId });
actions.closeStarsBalanceModal({ tabId });
actions.closeStarsTransactionModal({ tabId }); actions.closeStarsTransactionModal({ tabId });
if (!currentMessageList || ( if (!currentMessageList || (

View File

@ -29,10 +29,10 @@ import {
getMediaHash, getMediaHash,
getMessageDownloadableMedia, getMessageDownloadableMedia,
getMessageStatefulContent, getMessageStatefulContent,
getPeerTitle,
isChatChannel, isChatChannel,
} from '../../helpers'; } from '../../helpers';
import { getMessageSummaryText } from '../../helpers/messageSummary'; import { getMessageSummaryText } from '../../helpers/messageSummary';
import { getPeerTitle } from '../../helpers/peers';
import { renderMessageSummaryHtml } from '../../helpers/renderMessageSummaryHtml'; import { renderMessageSummaryHtml } from '../../helpers/renderMessageSummaryHtml';
import { addActionHandler, getGlobal, setGlobal } from '../../index'; import { addActionHandler, getGlobal, setGlobal } from '../../index';
import { import {
@ -62,7 +62,6 @@ import {
selectIsRightColumnShown, selectIsRightColumnShown,
selectIsViewportNewest, selectIsViewportNewest,
selectMessageIdsByGroupId, selectMessageIdsByGroupId,
selectPeer,
selectPinnedIds, selectPinnedIds,
selectReplyStack, selectReplyStack,
selectRequestedChatTranslationLanguage, selectRequestedChatTranslationLanguage,
@ -1115,8 +1114,7 @@ addActionHandler('updateSharePreparedMessageModalSendArgs', async (global, actio
return; return;
} }
const peer = selectPeer(global, args.peerId); const starsForSendMessage = await getPeerStarsForMessage(global, args.peerId);
const starsForSendMessage = peer ? await getPeerStarsForMessage(global, peer) : undefined;
global = getGlobal(); global = getGlobal();
global = updateTabState(global, { global = updateTabState(global, {

View File

@ -32,6 +32,7 @@ import {
selectCurrentMessageList, selectCurrentMessageList,
selectIsCurrentUserPremium, selectIsCurrentUserPremium,
selectIsTrustedBot, selectIsTrustedBot,
selectPeerPaidMessagesStars,
selectSender, selectSender,
selectTabState, selectTabState,
selectTopic, selectTopic,
@ -325,7 +326,19 @@ addActionHandler('showNotification', (global, actions, payload): ActionReturnTyp
}); });
addActionHandler('showAllowedMessageTypesNotification', (global, actions, payload): ActionReturnType => { addActionHandler('showAllowedMessageTypesNotification', (global, actions, payload): ActionReturnType => {
const { chatId, tabId = getCurrentTabId() } = payload; const { chatId, messageListType, tabId = getCurrentTabId() } = payload;
const paidMessagesStars = selectPeerPaidMessagesStars(global, chatId);
if (paidMessagesStars && messageListType === 'scheduled') {
actions.showNotification({
message: {
key: 'DescriptionScheduledPaidMessagesNotAllowed',
},
tabId,
});
return;
}
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
if (!chat) return; if (!chat) return;

View File

@ -9,7 +9,6 @@ import { addActionHandler, getGlobal, setGlobal } from '../../index';
import { addStoriesForPeer } from '../../reducers'; import { addStoriesForPeer } from '../../reducers';
import { updateTabState } from '../../reducers/tabs'; import { updateTabState } from '../../reducers/tabs';
import { import {
selectChat,
selectCurrentViewedStory, selectCurrentViewedStory,
selectPeer, selectPeer,
selectPeerFirstStoryId, selectPeerFirstStoryId,
@ -296,13 +295,11 @@ addActionHandler('sendMessage', async (global, actions, payload): Promise<void>
const { storyId, peerId: storyPeerId } = selectCurrentViewedStory(global, tabId); const { storyId, peerId: storyPeerId } = selectCurrentViewedStory(global, tabId);
const isStoryReply = Boolean(storyId && storyPeerId); const isStoryReply = Boolean(storyId && storyPeerId);
const chat = storyPeerId ? selectChat(global, storyPeerId) : undefined; if (!isStoryReply) {
if (!chat) return;
const messagePriceInStars = await getPeerStarsForMessage(global, chat);
if (!isStoryReply || messagePriceInStars) {
return; return;
} }
const messagePriceInStars = await getPeerStarsForMessage(global, storyPeerId!);
if (messagePriceInStars === undefined) return;
const { gif, sticker, isReaction } = payload; const { gif, sticker, isReaction } = payload;

View File

@ -1,6 +1,7 @@
import type { ActionReturnType } from '../../types'; import type { ActionReturnType } from '../../types';
import { getCurrentTabId } from '../../../util/establishMultitabRole'; import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { addTabStateResetterAction } from '../../helpers/meta';
import { addActionHandler } from '../../index'; import { addActionHandler } from '../../index';
import { closeNewContactDialog, updateUserSearch } from '../../reducers'; import { closeNewContactDialog, updateUserSearch } from '../../reducers';
import { updateTabState } from '../../reducers/tabs'; import { updateTabState } from '../../reducers/tabs';
@ -50,3 +51,5 @@ addActionHandler('closeSuggestedStatusModal', (global, actions, payload): Action
suggestedStatusModal: undefined, suggestedStatusModal: undefined,
}, tabId); }, tabId);
}); });
addTabStateResetterAction('closeChatRefundModal', 'chatRefundModal');

View File

@ -9,7 +9,6 @@ import type {
ApiPeer, ApiPeer,
ApiPreparedInlineMessage, ApiPreparedInlineMessage,
ApiTopic, ApiTopic,
ApiUser,
} from '../../api/types'; } from '../../api/types';
import type { OldLangFn } from '../../hooks/useOldLang'; import type { OldLangFn } from '../../hooks/useOldLang';
import type { import type {
@ -27,7 +26,7 @@ import { formatDateToString, formatTime } from '../../util/dates/dateFormat';
import { getServerTime } from '../../util/serverTime'; import { getServerTime } from '../../util/serverTime';
import { getGlobal } from '..'; import { getGlobal } from '..';
import { isSystemBot } from './bots'; import { isSystemBot } from './bots';
import { getMainUsername, getUserFirstOrLastName } from './users'; import { getMainUsername } from './users';
const FOREVER_BANNED_DATE = Date.now() / 1000 + 31622400; // 366 days const FOREVER_BANNED_DATE = Date.now() / 1000 + 31622400; // 366 days
@ -35,14 +34,6 @@ export function isUserId(entityId: string) {
return !entityId.startsWith('-'); return !entityId.startsWith('-');
} }
export function isPeerChat(entity: ApiPeer): entity is ApiChat {
return 'title' in entity;
}
export function isPeerUser(entity: ApiPeer): entity is ApiUser {
return !isPeerChat(entity);
}
export function isChannelId(entityId: string) { export function isChannelId(entityId: string) {
return entityId.length === CHANNEL_ID_LENGTH && entityId.startsWith('-1'); return entityId.length === CHANNEL_ID_LENGTH && entityId.startsWith('-1');
} }
@ -211,8 +202,10 @@ export function getAllowedAttachmentOptions(
chatFullInfo?: ApiChatFullInfo, chatFullInfo?: ApiChatFullInfo,
isChatWithBot = false, isChatWithBot = false,
isStoryReply = false, isStoryReply = false,
paidMessagesStars?: number,
isInScheduledList = false,
): IAllowedAttachmentOptions { ): IAllowedAttachmentOptions {
if (!chat) { if (!chat || (paidMessagesStars && isInScheduledList)) {
return { return {
canAttachMedia: false, canAttachMedia: false,
canAttachPolls: false, canAttachPolls: false,
@ -345,24 +338,6 @@ export function getFolderDescriptionText(lang: OldLangFn, folder: ApiChatFolder,
} }
} }
export function getMessageSenderName(lang: OldLangFn, chatId: string, sender?: ApiPeer) {
if (!sender || isUserId(chatId)) {
return undefined;
}
if (isPeerChat(sender)) {
if (chatId === sender.id) return undefined;
return sender.title;
}
if (sender.isSelf) {
return lang('FromYou');
}
return getUserFirstOrLastName(sender);
}
export function isChatPublic(chat: ApiChat) { export function isChatPublic(chat: ApiChat) {
return chat.usernames?.some(({ isActive }) => isActive); return chat.usernames?.some(({ isActive }) => isActive);
} }

View File

@ -9,9 +9,7 @@ import type {
import type { import type {
ApiPoll, MediaContainer, StatefulMediaContent, ApiPoll, MediaContainer, StatefulMediaContent,
} from '../../api/types/messages'; } from '../../api/types/messages';
import type { OldLangFn } from '../../hooks/useOldLang'; import type { ThreadId } from '../../types';
import type { CustomPeer, ThreadId } from '../../types';
import type { LangFn } from '../../util/localization';
import type { GlobalState } from '../types'; import type { GlobalState } from '../types';
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types'; import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types';
@ -32,9 +30,9 @@ import { isLocalMessageId } from '../../util/keys/messageKey';
import { getServerTime } from '../../util/serverTime'; import { getServerTime } from '../../util/serverTime';
import { getGlobal } from '../index'; import { getGlobal } from '../index';
import { import {
getChatTitle, getCleanPeerId, isPeerUser, isUserId, getCleanPeerId, isUserId,
} from './chats'; } from './chats';
import { getMainUsername, getUserFirstOrLastName, getUserFullName } from './users'; import { getMainUsername } from './users';
const RE_LINK = new RegExp(RE_LINK_TEMPLATE, 'i'); const RE_LINK = new RegExp(RE_LINK_TEMPLATE, 'i');
@ -209,24 +207,6 @@ export function isAnonymousOwnMessage(message: ApiMessage) {
return Boolean(message.senderId) && !isUserId(message.senderId) && isOwnMessage(message); return Boolean(message.senderId) && !isUserId(message.senderId) && isOwnMessage(message);
} }
export function getPeerTitle(lang: OldLangFn | LangFn, peer: ApiPeer | CustomPeer) {
if (!peer) return undefined;
if ('isCustomPeer' in peer) {
// TODO: Remove any after full migration to new lang
return peer.titleKey ? lang(peer.titleKey as any) : peer.title;
}
return isPeerUser(peer) ? getUserFirstOrLastName(peer) : getChatTitle(lang, peer);
}
export function getPeerFullTitle(lang: OldLangFn | LangFn, peer: ApiPeer | CustomPeer) {
if (!peer) return undefined;
if ('isCustomPeer' in peer) {
// TODO: Remove any after full migration to new lang
return peer.titleKey ? lang(peer.titleKey as any) : peer.title;
}
return isPeerUser(peer) ? getUserFullName(peer) : getChatTitle(lang, peer);
}
export function getSendingState(message: ApiMessage) { export function getSendingState(message: ApiMessage) {
if (!message.sendingState) { if (!message.sendingState) {
return 'succeeded'; return 'succeeded';

View File

@ -1,12 +1,14 @@
import type { ApiChat, ApiPeer, ApiUser } from '../../api/types'; import type { ApiChat, ApiPeer, ApiUser } from '../../api/types';
import type { OldLangFn } from '../../hooks/useOldLang';
import type { CustomPeer } from '../../types';
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config'; import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
import { getTranslationFn } from '../../util/localization'; import { getTranslationFn, type LangFn } from '../../util/localization';
import { prepareSearchWordsForNeedle } from '../../util/searchWords'; import { prepareSearchWordsForNeedle } from '../../util/searchWords';
import { selectChat, selectPeer, selectUser } from '../selectors'; import { selectChat, selectPeer, selectUser } from '../selectors';
import { getGlobal } from '..'; import { getGlobal } from '..';
import { getChatTitle } from './chats'; import { getChatTitle, isUserId } from './chats';
import { getPeerFullTitle } from './messages'; import { getUserFirstOrLastName, getUserFullName } from './users';
export function isApiPeerChat(peer: ApiPeer): peer is ApiChat { export function isApiPeerChat(peer: ApiPeer): peer is ApiChat {
return 'title' in peer; return 'title' in peer;
@ -89,3 +91,39 @@ export function getPeerTypeKey(peer: ApiPeer) {
return 'ChatList.PeerTypeNonContactUser'; return 'ChatList.PeerTypeNonContactUser';
} }
export function getPeerTitle(lang: OldLangFn | LangFn, peer: ApiPeer | CustomPeer) {
if (!peer) return undefined;
if ('isCustomPeer' in peer) {
// TODO: Remove any after full migration to new lang
return peer.titleKey ? lang(peer.titleKey as any) : peer.title;
}
return isApiPeerUser(peer) ? getUserFirstOrLastName(peer) : getChatTitle(lang, peer);
}
export function getPeerFullTitle(lang: OldLangFn | LangFn, peer: ApiPeer | CustomPeer) {
if (!peer) return undefined;
if ('isCustomPeer' in peer) {
// TODO: Remove any after full migration to new lang
return peer.titleKey ? lang(peer.titleKey as any) : peer.title;
}
return isApiPeerUser(peer) ? getUserFullName(peer) : getChatTitle(lang, peer);
}
export function getMessageSenderName(lang: OldLangFn, chatId: string, sender?: ApiPeer) {
if (!sender || isUserId(chatId)) {
return undefined;
}
if (isApiPeerChat(sender)) {
if (chatId === sender.id) return undefined;
return sender.title;
}
if (sender.isSelf) {
return lang('FromYou');
}
return getUserFirstOrLastName(sender);
}

View File

@ -1741,6 +1741,10 @@ export interface ActionPayloads {
userId: string; userId: string;
shouldRefundCharged: boolean; shouldRefundCharged: boolean;
}; };
openChatRefundModal: {
userId: string;
} & WithTabId;
closeChatRefundModal: WithTabId | undefined;
loadMoreProfilePhotos: { loadMoreProfilePhotos: {
peerId: string; peerId: string;
isPreload?: boolean; isPreload?: boolean;
@ -2186,6 +2190,7 @@ export interface ActionPayloads {
showNotification: Omit<ApiNotification, 'localId'> & { localId?: string } & WithTabId; showNotification: Omit<ApiNotification, 'localId'> & { localId?: string } & WithTabId;
showAllowedMessageTypesNotification: { showAllowedMessageTypesNotification: {
chatId: string; chatId: string;
messageListType?: MessageListType;
} & WithTabId; } & WithTabId;
dismissNotification: { localId: string } & WithTabId; dismissNotification: { localId: string } & WithTabId;

View File

@ -638,6 +638,10 @@ export type TabState = {
forPeerId: string; forPeerId: string;
gifts?: ApiPremiumGiftCodeOption[]; gifts?: ApiPremiumGiftCodeOption[];
}; };
chatRefundModal?: {
userId: string;
starsToRefund: number;
};
limitReachedModal?: { limitReachedModal?: {
limit: ApiLimitTypeWithModal; limit: ApiLimitTypeWithModal;

View File

@ -6,8 +6,9 @@ import type {
} from '../api/types'; } from '../api/types';
import { import {
getAudioHasCover, getChatAvatarHash, getChatTitle, getMediaHash, getMessageContent, getPeerTitle, getAudioHasCover, getChatAvatarHash, getChatTitle, getMediaHash, getMessageContent,
} from '../global/helpers'; } from '../global/helpers';
import { getPeerTitle } from '../global/helpers/peers';
import { resizeImage, scaleImage } from '../util/imageResize'; import { resizeImage, scaleImage } from '../util/imageResize';
import { buildMediaMetadata } from '../util/mediaSession'; import { buildMediaMetadata } from '../util/mediaSession';
import { AVATAR_FULL_DIMENSIONS } from '../components/common/helpers/mediaDimensions'; import { AVATAR_FULL_DIMENSIONS } from '../components/common/helpers/mediaDimensions';

View File

@ -1473,6 +1473,7 @@ account.resolveBusinessChatLink#5492e5ee slug:string = account.ResolvedBusinessC
account.toggleSponsoredMessages#b9d9a38d enabled:Bool = Bool; account.toggleSponsoredMessages#b9d9a38d enabled:Bool = Bool;
account.getCollectibleEmojiStatuses#2e7b4543 hash:long = account.EmojiStatuses; account.getCollectibleEmojiStatuses#2e7b4543 hash:long = account.EmojiStatuses;
account.addNoPaidMessagesException#6f688aa7 flags:# refund_charged:flags.0?true user_id:InputUser = Bool; account.addNoPaidMessagesException#6f688aa7 flags:# refund_charged:flags.0?true user_id:InputUser = Bool;
account.getPaidMessagesRevenue#f1266f38 user_id:InputUser = account.PaidMessagesRevenue;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>; users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
users.getFullUser#b60f5918 id:InputUser = users.UserFull; users.getFullUser#b60f5918 id:InputUser = users.UserFull;
users.getRequirementsToContact#d89a83a3 id:Vector<InputUser> = Vector<RequirementToContact>; users.getRequirementsToContact#d89a83a3 id:Vector<InputUser> = Vector<RequirementToContact>;

View File

@ -62,6 +62,7 @@
"account.toggleSponsoredMessages", "account.toggleSponsoredMessages",
"account.getCollectibleEmojiStatuses", "account.getCollectibleEmojiStatuses",
"account.addNoPaidMessagesException", "account.addNoPaidMessagesException",
"account.getPaidMessagesRevenue",
"users.getUsers", "users.getUsers",
"users.getFullUser", "users.getFullUser",
"contacts.getContacts", "contacts.getContacts",

View File

@ -103,7 +103,6 @@ $color-message-story-mention-to: #74bcff;
--color-voice-transcribe-button: #e8f3ff; --color-voice-transcribe-button: #e8f3ff;
--color-voice-transcribe-button-own: #cceebf; --color-voice-transcribe-button-own: #cceebf;
--color-toast-action: #64D1FF;
--color-primary: #{$color-primary}; --color-primary: #{$color-primary};
--color-primary-shade: #{color.mix($color-primary, $color-black, 92%)}; --color-primary-shade: #{color.mix($color-primary, $color-black, 92%)};
--color-primary-shade-darker: #{color.mix($color-primary, $color-black, 84%)}; --color-primary-shade-darker: #{color.mix($color-primary, $color-black, 84%)};

View File

@ -1447,6 +1447,9 @@ export interface LangPair {
'StoryTooltipReactionSent': undefined; 'StoryTooltipReactionSent': undefined;
'StarsNeededTextSendPaidMessages': undefined; 'StarsNeededTextSendPaidMessages': undefined;
'PaidMessageTransactionTotal': undefined; 'PaidMessageTransactionTotal': undefined;
'DescriptionRestrictedMedia': undefined;
'DescriptionScheduledPaidMediaNotAllowed': undefined;
'DescriptionScheduledPaidMessagesNotAllowed': undefined;
} }
export interface LangPairWithVariables<V extends unknown = LangVariable> { export interface LangPairWithVariables<V extends unknown = LangVariable> {

View File

@ -12,12 +12,12 @@ import {
getChatAvatarHash, getChatAvatarHash,
getChatTitle, getChatTitle,
getMessageRecentReaction, getMessageRecentReaction,
getMessageSenderName,
getPrivateChatUserId, getPrivateChatUserId,
getUserFullName, getUserFullName,
isChatChannel, isChatChannel,
} from '../global/helpers'; } from '../global/helpers';
import { getIsChatMuted, getIsChatSilent, getShouldShowMessagePreview } from '../global/helpers/notifications'; import { getIsChatMuted, getIsChatSilent, getShouldShowMessagePreview } from '../global/helpers/notifications';
import { getMessageSenderName } from '../global/helpers/peers';
import { import {
selectChat, selectChat,
selectCurrentMessageList, selectCurrentMessageList,