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,
|
about, commonChatsCount, pinnedMsgId, botInfo, blocked,
|
||||||
profilePhoto, voiceMessagesForbidden, premiumGifts,
|
profilePhoto, voiceMessagesForbidden, premiumGifts,
|
||||||
fallbackPhoto, personalPhoto, translationsDisabled, storiesPinnedAvailable,
|
fallbackPhoto, personalPhoto, translationsDisabled, storiesPinnedAvailable,
|
||||||
|
contactRequirePremium,
|
||||||
},
|
},
|
||||||
users,
|
users,
|
||||||
} = mtpUserFull;
|
} = mtpUserFull;
|
||||||
@ -37,6 +38,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
|
|||||||
personalPhoto: personalPhoto instanceof GramJs.Photo ? buildApiPhoto(personalPhoto) : undefined,
|
personalPhoto: personalPhoto instanceof GramJs.Photo ? buildApiPhoto(personalPhoto) : undefined,
|
||||||
...(premiumGifts && { premiumGifts: premiumGifts.map((gift) => buildApiPremiumGiftOption(gift)) }),
|
...(premiumGifts && { premiumGifts: premiumGifts.map((gift) => buildApiPremiumGiftOption(gift)) }),
|
||||||
...(botInfo && { botInfo: buildApiBotInfo(botInfo, userId) }),
|
...(botInfo && { botInfo: buildApiBotInfo(botInfo, userId) }),
|
||||||
|
isContactRequirePremium: contactRequirePremium,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -394,6 +394,10 @@ export function sendMessage(
|
|||||||
});
|
});
|
||||||
if (update) handleLocalMessageUpdate(localMessage, update);
|
if (update) handleLocalMessageUpdate(localMessage, update);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
if (error.message === 'PRIVACY_PREMIUM_REQUIRED') {
|
||||||
|
onUpdate({ '@type': 'updateRequestUserUpdate', id: chat.id });
|
||||||
|
}
|
||||||
|
|
||||||
onUpdate({
|
onUpdate({
|
||||||
'@type': 'updateMessageSendFailed',
|
'@type': 'updateMessageSendFailed',
|
||||||
chatId: chat.id,
|
chatId: chat.id,
|
||||||
|
|||||||
@ -53,6 +53,7 @@ export interface ApiUserFullInfo {
|
|||||||
premiumGifts?: ApiPremiumGiftOption[];
|
premiumGifts?: ApiPremiumGiftOption[];
|
||||||
isTranslationDisabled?: true;
|
isTranslationDisabled?: true;
|
||||||
hasPinnedStories?: boolean;
|
hasPinnedStories?: boolean;
|
||||||
|
isContactRequirePremium?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiFakeType = 'fake' | 'scam';
|
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 FoldersNew from '../../../assets/tgs/settings/FoldersNew.tgs';
|
||||||
import FoldersShare from '../../../assets/tgs/settings/FoldersShare.tgs';
|
import FoldersShare from '../../../assets/tgs/settings/FoldersShare.tgs';
|
||||||
import Lock from '../../../assets/tgs/settings/Lock.tgs';
|
import Lock from '../../../assets/tgs/settings/Lock.tgs';
|
||||||
|
import Unlock from '../../../assets/tgs/Unlock.tgs';
|
||||||
|
|
||||||
export const LOCAL_TGS_URLS = {
|
export const LOCAL_TGS_URLS = {
|
||||||
MonkeyIdle,
|
MonkeyIdle,
|
||||||
@ -50,4 +51,5 @@ export const LOCAL_TGS_URLS = {
|
|||||||
PartyPopper,
|
PartyPopper,
|
||||||
Flame,
|
Flame,
|
||||||
ReadTime,
|
ReadTime,
|
||||||
|
Unlock,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -79,6 +79,7 @@ import ContactGreeting from './ContactGreeting';
|
|||||||
import MessageListBotInfo from './MessageListBotInfo';
|
import MessageListBotInfo from './MessageListBotInfo';
|
||||||
import MessageListContent from './MessageListContent';
|
import MessageListContent from './MessageListContent';
|
||||||
import NoMessages from './NoMessages';
|
import NoMessages from './NoMessages';
|
||||||
|
import PremiumRequiredMessage from './PremiumRequiredMessage';
|
||||||
|
|
||||||
import './MessageList.scss';
|
import './MessageList.scss';
|
||||||
|
|
||||||
@ -96,6 +97,7 @@ type OwnProps = {
|
|||||||
withDefaultBg: boolean;
|
withDefaultBg: boolean;
|
||||||
onPinnedIntersectionChange: PinnedIntersectionChangedCallback;
|
onPinnedIntersectionChange: PinnedIntersectionChangedCallback;
|
||||||
getForceNextPinnedInHeader: Signal<boolean | undefined>;
|
getForceNextPinnedInHeader: Signal<boolean | undefined>;
|
||||||
|
isContactRequirePremium?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -181,6 +183,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
currentUserId,
|
currentUserId,
|
||||||
getForceNextPinnedInHeader,
|
getForceNextPinnedInHeader,
|
||||||
onPinnedIntersectionChange,
|
onPinnedIntersectionChange,
|
||||||
|
isContactRequirePremium,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadViewportMessages, setScrollOffset, loadSponsoredMessages, loadMessageReactions, copyMessagesByIds,
|
loadViewportMessages, setScrollOffset, loadSponsoredMessages, loadMessageReactions, copyMessagesByIds,
|
||||||
@ -607,6 +610,8 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
{restrictionReason ? restrictionReason.text : `This is a private ${isChannelChat ? 'channel' : 'chat'}`}
|
{restrictionReason ? restrictionReason.text : `This is a private ${isChannelChat ? 'channel' : 'chat'}`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
) : isContactRequirePremium ? (
|
||||||
|
<PremiumRequiredMessage userId={chatId} />
|
||||||
) : isBot && !hasMessages ? (
|
) : isBot && !hasMessages ? (
|
||||||
<MessageListBotInfo chatId={chatId} />
|
<MessageListBotInfo chatId={chatId} />
|
||||||
) : shouldRenderGreeting ? (
|
) : shouldRenderGreeting ? (
|
||||||
|
|||||||
@ -55,6 +55,7 @@ import {
|
|||||||
selectTabState,
|
selectTabState,
|
||||||
selectTheme,
|
selectTheme,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
|
selectUserFullInfo,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import buildStyle from '../../util/buildStyle';
|
import buildStyle from '../../util/buildStyle';
|
||||||
@ -92,6 +93,7 @@ import MessageList from './MessageList';
|
|||||||
import MessageSelectToolbar from './MessageSelectToolbar.async';
|
import MessageSelectToolbar from './MessageSelectToolbar.async';
|
||||||
import MiddleHeader from './MiddleHeader';
|
import MiddleHeader from './MiddleHeader';
|
||||||
import MobileSearch from './MobileSearch.async';
|
import MobileSearch from './MobileSearch.async';
|
||||||
|
import PremiumRequiredPlaceholder from './PremiumRequiredPlaceholder';
|
||||||
import ReactorListModal from './ReactorListModal.async';
|
import ReactorListModal from './ReactorListModal.async';
|
||||||
|
|
||||||
import './MiddleColumn.scss';
|
import './MiddleColumn.scss';
|
||||||
@ -151,6 +153,7 @@ type StateProps = {
|
|||||||
canUnblock?: boolean;
|
canUnblock?: boolean;
|
||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
canShowOpenChatButton?: boolean;
|
canShowOpenChatButton?: boolean;
|
||||||
|
isContactRequirePremium?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isImage(item: DataTransferItem) {
|
function isImage(item: DataTransferItem) {
|
||||||
@ -210,6 +213,7 @@ function MiddleColumn({
|
|||||||
canUnblock,
|
canUnblock,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
canShowOpenChatButton,
|
canShowOpenChatButton,
|
||||||
|
isContactRequirePremium,
|
||||||
}: OwnProps & StateProps) {
|
}: OwnProps & StateProps) {
|
||||||
const {
|
const {
|
||||||
openChat,
|
openChat,
|
||||||
@ -267,7 +271,7 @@ function MiddleColumn({
|
|||||||
const renderingCanUnblock = usePrevDuringAnimation(canUnblock, closeAnimationDuration);
|
const renderingCanUnblock = usePrevDuringAnimation(canUnblock, closeAnimationDuration);
|
||||||
const renderingCanPost = usePrevDuringAnimation(canPost, closeAnimationDuration)
|
const renderingCanPost = usePrevDuringAnimation(canPost, closeAnimationDuration)
|
||||||
&& !renderingCanRestartBot && !renderingCanStartBot && !renderingCanSubscribe && !renderingCanUnblock
|
&& !renderingCanRestartBot && !renderingCanStartBot && !renderingCanSubscribe && !renderingCanUnblock
|
||||||
&& chatId !== TMP_CHAT_ID;
|
&& chatId !== TMP_CHAT_ID && !isContactRequirePremium;
|
||||||
const renderingHasTools = usePrevDuringAnimation(hasTools, closeAnimationDuration);
|
const renderingHasTools = usePrevDuringAnimation(hasTools, closeAnimationDuration);
|
||||||
const renderingIsFabShown = usePrevDuringAnimation(isFabShown, closeAnimationDuration) && chatId !== TMP_CHAT_ID;
|
const renderingIsFabShown = usePrevDuringAnimation(isFabShown, closeAnimationDuration) && chatId !== TMP_CHAT_ID;
|
||||||
const renderingIsChannel = usePrevDuringAnimation(isChannel, closeAnimationDuration);
|
const renderingIsChannel = usePrevDuringAnimation(isChannel, closeAnimationDuration);
|
||||||
@ -449,7 +453,9 @@ function MiddleColumn({
|
|||||||
);
|
);
|
||||||
const forumComposerPlaceholder = getForumComposerPlaceholder(lang, chat, threadId, Boolean(draftReplyInfo));
|
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
|
// CSS Variables calculation doesn't work properly with transforms, so we calculate transform values in JS
|
||||||
const {
|
const {
|
||||||
@ -549,6 +555,7 @@ function MiddleColumn({
|
|||||||
onFabToggle={setIsFabShown}
|
onFabToggle={setIsFabShown}
|
||||||
onNotchToggle={setIsNotchShown}
|
onNotchToggle={setIsNotchShown}
|
||||||
isReady={isReady}
|
isReady={isReady}
|
||||||
|
isContactRequirePremium={isContactRequirePremium}
|
||||||
withBottomShift={withMessageListBottomShift}
|
withBottomShift={withMessageListBottomShift}
|
||||||
withDefaultBg={Boolean(!customBackground && !backgroundColor)}
|
withDefaultBg={Boolean(!customBackground && !backgroundColor)}
|
||||||
onPinnedIntersectionChange={renderingOnPinnedIntersectionChange!}
|
onPinnedIntersectionChange={renderingOnPinnedIntersectionChange!}
|
||||||
@ -805,6 +812,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isContactRequirePremium = selectUserFullInfo(global, chatId)?.isContactRequirePremium;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
chatId,
|
chatId,
|
||||||
@ -815,7 +824,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isPrivate,
|
isPrivate,
|
||||||
areChatSettingsLoaded: Boolean(chat?.settings),
|
areChatSettingsLoaded: Boolean(chat?.settings),
|
||||||
isComments: isMessageThread,
|
isComments: isMessageThread,
|
||||||
canPost: !isPinnedMessageList
|
canPost:
|
||||||
|
!isPinnedMessageList
|
||||||
&& (!chat || canPost)
|
&& (!chat || canPost)
|
||||||
&& !isBotNotStarted
|
&& !isBotNotStarted
|
||||||
&& !(shouldJoinToSend && chat?.isNotJoined)
|
&& !(shouldJoinToSend && chat?.isNotJoined)
|
||||||
@ -842,6 +852,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canUnblock,
|
canUnblock,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
canShowOpenChatButton,
|
canShowOpenChatButton,
|
||||||
|
isContactRequirePremium,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(MiddleColumn));
|
)(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