Messages: Users who aren't contacts or don't have Premium can't start chats (#4308)
This commit is contained in:
parent
45d019b5f8
commit
90e6470eed
@ -18,6 +18,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
|
||||
about, commonChatsCount, pinnedMsgId, botInfo, blocked,
|
||||
profilePhoto, voiceMessagesForbidden, premiumGifts,
|
||||
fallbackPhoto, personalPhoto, translationsDisabled, storiesPinnedAvailable,
|
||||
contactRequirePremium,
|
||||
},
|
||||
users,
|
||||
} = mtpUserFull;
|
||||
@ -37,6 +38,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
|
||||
personalPhoto: personalPhoto instanceof GramJs.Photo ? buildApiPhoto(personalPhoto) : undefined,
|
||||
...(premiumGifts && { premiumGifts: premiumGifts.map((gift) => buildApiPremiumGiftOption(gift)) }),
|
||||
...(botInfo && { botInfo: buildApiBotInfo(botInfo, userId) }),
|
||||
isContactRequirePremium: contactRequirePremium,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -394,6 +394,10 @@ export function sendMessage(
|
||||
});
|
||||
if (update) handleLocalMessageUpdate(localMessage, update);
|
||||
} catch (error: any) {
|
||||
if (error.message === 'PRIVACY_PREMIUM_REQUIRED') {
|
||||
onUpdate({ '@type': 'updateRequestUserUpdate', id: chat.id });
|
||||
}
|
||||
|
||||
onUpdate({
|
||||
'@type': 'updateMessageSendFailed',
|
||||
chatId: chat.id,
|
||||
|
||||
@ -53,6 +53,7 @@ export interface ApiUserFullInfo {
|
||||
premiumGifts?: ApiPremiumGiftOption[];
|
||||
isTranslationDisabled?: true;
|
||||
hasPinnedStories?: boolean;
|
||||
isContactRequirePremium?: boolean;
|
||||
}
|
||||
|
||||
export type ApiFakeType = 'fake' | 'scam';
|
||||
|
||||
BIN
src/assets/tgs/Unlock.tgs
Normal file
BIN
src/assets/tgs/Unlock.tgs
Normal file
Binary file not shown.
@ -23,6 +23,7 @@ import FoldersAll from '../../../assets/tgs/settings/FoldersAll.tgs';
|
||||
import FoldersNew from '../../../assets/tgs/settings/FoldersNew.tgs';
|
||||
import FoldersShare from '../../../assets/tgs/settings/FoldersShare.tgs';
|
||||
import Lock from '../../../assets/tgs/settings/Lock.tgs';
|
||||
import Unlock from '../../../assets/tgs/Unlock.tgs';
|
||||
|
||||
export const LOCAL_TGS_URLS = {
|
||||
MonkeyIdle,
|
||||
@ -50,4 +51,5 @@ export const LOCAL_TGS_URLS = {
|
||||
PartyPopper,
|
||||
Flame,
|
||||
ReadTime,
|
||||
Unlock,
|
||||
};
|
||||
|
||||
@ -79,6 +79,7 @@ import ContactGreeting from './ContactGreeting';
|
||||
import MessageListBotInfo from './MessageListBotInfo';
|
||||
import MessageListContent from './MessageListContent';
|
||||
import NoMessages from './NoMessages';
|
||||
import PremiumRequiredMessage from './PremiumRequiredMessage';
|
||||
|
||||
import './MessageList.scss';
|
||||
|
||||
@ -96,6 +97,7 @@ type OwnProps = {
|
||||
withDefaultBg: boolean;
|
||||
onPinnedIntersectionChange: PinnedIntersectionChangedCallback;
|
||||
getForceNextPinnedInHeader: Signal<boolean | undefined>;
|
||||
isContactRequirePremium?: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -181,6 +183,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
currentUserId,
|
||||
getForceNextPinnedInHeader,
|
||||
onPinnedIntersectionChange,
|
||||
isContactRequirePremium,
|
||||
}) => {
|
||||
const {
|
||||
loadViewportMessages, setScrollOffset, loadSponsoredMessages, loadMessageReactions, copyMessagesByIds,
|
||||
@ -607,6 +610,8 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
{restrictionReason ? restrictionReason.text : `This is a private ${isChannelChat ? 'channel' : 'chat'}`}
|
||||
</span>
|
||||
</div>
|
||||
) : isContactRequirePremium ? (
|
||||
<PremiumRequiredMessage userId={chatId} />
|
||||
) : isBot && !hasMessages ? (
|
||||
<MessageListBotInfo chatId={chatId} />
|
||||
) : shouldRenderGreeting ? (
|
||||
|
||||
@ -55,6 +55,7 @@ import {
|
||||
selectTabState,
|
||||
selectTheme,
|
||||
selectThreadInfo,
|
||||
selectUserFullInfo,
|
||||
} from '../../global/selectors';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import buildStyle from '../../util/buildStyle';
|
||||
@ -92,6 +93,7 @@ import MessageList from './MessageList';
|
||||
import MessageSelectToolbar from './MessageSelectToolbar.async';
|
||||
import MiddleHeader from './MiddleHeader';
|
||||
import MobileSearch from './MobileSearch.async';
|
||||
import PremiumRequiredPlaceholder from './PremiumRequiredPlaceholder';
|
||||
import ReactorListModal from './ReactorListModal.async';
|
||||
|
||||
import './MiddleColumn.scss';
|
||||
@ -151,6 +153,7 @@ type StateProps = {
|
||||
canUnblock?: boolean;
|
||||
isSavedDialog?: boolean;
|
||||
canShowOpenChatButton?: boolean;
|
||||
isContactRequirePremium?: boolean;
|
||||
};
|
||||
|
||||
function isImage(item: DataTransferItem) {
|
||||
@ -210,6 +213,7 @@ function MiddleColumn({
|
||||
canUnblock,
|
||||
isSavedDialog,
|
||||
canShowOpenChatButton,
|
||||
isContactRequirePremium,
|
||||
}: OwnProps & StateProps) {
|
||||
const {
|
||||
openChat,
|
||||
@ -267,7 +271,7 @@ function MiddleColumn({
|
||||
const renderingCanUnblock = usePrevDuringAnimation(canUnblock, closeAnimationDuration);
|
||||
const renderingCanPost = usePrevDuringAnimation(canPost, closeAnimationDuration)
|
||||
&& !renderingCanRestartBot && !renderingCanStartBot && !renderingCanSubscribe && !renderingCanUnblock
|
||||
&& chatId !== TMP_CHAT_ID;
|
||||
&& chatId !== TMP_CHAT_ID && !isContactRequirePremium;
|
||||
const renderingHasTools = usePrevDuringAnimation(hasTools, closeAnimationDuration);
|
||||
const renderingIsFabShown = usePrevDuringAnimation(isFabShown, closeAnimationDuration) && chatId !== TMP_CHAT_ID;
|
||||
const renderingIsChannel = usePrevDuringAnimation(isChannel, closeAnimationDuration);
|
||||
@ -449,7 +453,9 @@ function MiddleColumn({
|
||||
);
|
||||
const forumComposerPlaceholder = getForumComposerPlaceholder(lang, chat, threadId, Boolean(draftReplyInfo));
|
||||
|
||||
const composerRestrictionMessage = messageSendingRestrictionReason || forumComposerPlaceholder;
|
||||
const composerRestrictionMessage = messageSendingRestrictionReason
|
||||
?? forumComposerPlaceholder
|
||||
?? (isContactRequirePremium ? <PremiumRequiredPlaceholder userId={chatId!} /> : undefined);
|
||||
|
||||
// CSS Variables calculation doesn't work properly with transforms, so we calculate transform values in JS
|
||||
const {
|
||||
@ -549,6 +555,7 @@ function MiddleColumn({
|
||||
onFabToggle={setIsFabShown}
|
||||
onNotchToggle={setIsNotchShown}
|
||||
isReady={isReady}
|
||||
isContactRequirePremium={isContactRequirePremium}
|
||||
withBottomShift={withMessageListBottomShift}
|
||||
withDefaultBg={Boolean(!customBackground && !backgroundColor)}
|
||||
onPinnedIntersectionChange={renderingOnPinnedIntersectionChange!}
|
||||
@ -805,6 +812,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
)
|
||||
);
|
||||
|
||||
const isContactRequirePremium = selectUserFullInfo(global, chatId)?.isContactRequirePremium;
|
||||
|
||||
return {
|
||||
...state,
|
||||
chatId,
|
||||
@ -815,7 +824,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
isPrivate,
|
||||
areChatSettingsLoaded: Boolean(chat?.settings),
|
||||
isComments: isMessageThread,
|
||||
canPost: !isPinnedMessageList
|
||||
canPost:
|
||||
!isPinnedMessageList
|
||||
&& (!chat || canPost)
|
||||
&& !isBotNotStarted
|
||||
&& !(shouldJoinToSend && chat?.isNotJoined)
|
||||
@ -842,6 +852,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
canUnblock,
|
||||
isSavedDialog,
|
||||
canShowOpenChatButton,
|
||||
isContactRequirePremium,
|
||||
};
|
||||
},
|
||||
)(MiddleColumn));
|
||||
|
||||
66
src/components/middle/PremiumRequiredMessage.module.scss
Normal file
66
src/components/middle/PremiumRequiredMessage.module.scss
Normal file
@ -0,0 +1,66 @@
|
||||
.root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-white);
|
||||
|
||||
.button {
|
||||
background: var(--pattern-color);
|
||||
width: 10rem;
|
||||
margin-top: 0.5rem;
|
||||
text-transform: none;
|
||||
color: var(--color-white);
|
||||
height: 2.25rem;
|
||||
line-height: 2.25rem;
|
||||
transition: filter 150ms ease-in-out;
|
||||
|
||||
&:not(.disabled):not(:disabled):hover {
|
||||
background-color: var(--pattern-color);
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: var(--pattern-color);
|
||||
max-width: 15rem;
|
||||
padding: 0.75rem 0;
|
||||
border-radius: 1.5rem;
|
||||
|
||||
&[dir="rtl"] {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.icons-container {
|
||||
border-radius: 50%;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
background: var(--pattern-color);
|
||||
}
|
||||
|
||||
.animated-unlock {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.comments-icon {
|
||||
font-size: 5rem;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transform: translateY(1.75rem);
|
||||
}
|
||||
|
||||
.description {
|
||||
text-align: center;
|
||||
padding: 0 1rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
72
src/components/middle/PremiumRequiredMessage.tsx
Normal file
72
src/components/middle/PremiumRequiredMessage.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../global';
|
||||
|
||||
import { getUserFirstOrLastName } from '../../global/helpers';
|
||||
import { selectTheme, selectUser } from '../../global/selectors';
|
||||
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
|
||||
import renderText from '../common/helpers/renderText';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
|
||||
import AnimatedIconWithPreview from '../common/AnimatedIconWithPreview';
|
||||
import Icon from '../common/Icon';
|
||||
import Button from '../ui/Button';
|
||||
|
||||
import styles from './PremiumRequiredMessage.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
userId: string;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
patternColor?: string;
|
||||
userName?: string;
|
||||
};
|
||||
|
||||
function PremiumRequiredMessage({ patternColor, userName }: StateProps) {
|
||||
const lang = useLang();
|
||||
const { openPremiumModal } = getActions();
|
||||
|
||||
const handleOpenPremiumModal = useLastCallback(() => openPremiumModal());
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={styles.inner}>
|
||||
<div className={styles.iconsContainer}>
|
||||
<AnimatedIconWithPreview
|
||||
tgsUrl={LOCAL_TGS_URLS.Unlock}
|
||||
size={54}
|
||||
color={patternColor}
|
||||
className={styles.animatedUnlock}
|
||||
/>
|
||||
<Icon name="comments-sticker" className={styles.commentsIcon} />
|
||||
</div>
|
||||
<span className={styles.description}>
|
||||
{renderText(lang('MessageLockedPremium', userName), ['simple_markdown'])}
|
||||
</span>
|
||||
<Button
|
||||
color="translucent-black"
|
||||
size="tiny"
|
||||
onClick={handleOpenPremiumModal}
|
||||
className={styles.button}
|
||||
>
|
||||
{lang('MessagePremiumUnlock')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(
|
||||
withGlobal<OwnProps>((global, { userId }): StateProps => {
|
||||
const theme = selectTheme(global);
|
||||
const { patternColor } = global.settings.themes[theme] || {};
|
||||
const user = selectUser(global, userId);
|
||||
|
||||
return {
|
||||
patternColor,
|
||||
userName: getUserFirstOrLastName(user),
|
||||
};
|
||||
})(PremiumRequiredMessage),
|
||||
);
|
||||
42
src/components/middle/PremiumRequiredPlaceholder.tsx
Normal file
42
src/components/middle/PremiumRequiredPlaceholder.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../global';
|
||||
|
||||
import { getUserFirstOrLastName } from '../../global/helpers';
|
||||
import { selectUser } from '../../global/selectors';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
|
||||
import Link from '../ui/Link';
|
||||
|
||||
type OwnProps = {
|
||||
userId: string;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
userName?: string;
|
||||
};
|
||||
|
||||
function PremiumRequiredPlaceholder({ userName }: StateProps) {
|
||||
const lang = useLang();
|
||||
const { openPremiumModal } = getActions();
|
||||
|
||||
const handleOpenPremiumModal = useLastCallback(() => openPremiumModal());
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>{lang('Chat.MessagingRestrictedPlaceholder', userName)}</div>
|
||||
<Link isPrimary onClick={handleOpenPremiumModal}>{lang('Chat.MessagingRestrictedPlaceholderAction')}</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { userId }): StateProps => {
|
||||
const user = selectUser(global, userId);
|
||||
|
||||
return {
|
||||
userName: getUserFirstOrLastName(user),
|
||||
};
|
||||
},
|
||||
)(PremiumRequiredPlaceholder));
|
||||
Loading…
x
Reference in New Issue
Block a user