Premium Modal: Display user emoji status (#3776)

This commit is contained in:
Alexander Zinchuk 2023-09-04 04:05:23 +02:00
parent a7700015ac
commit 44c31ff6a0
8 changed files with 139 additions and 25 deletions

View File

@ -44,6 +44,7 @@ type OwnProps = {
noStatusOrTyping?: boolean;
noRtl?: boolean;
adminMember?: ApiChatMember;
onEmojiStatusClick?: NoneToVoidFunction;
};
type StateProps =
@ -75,6 +76,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
areMessagesLoaded,
adminMember,
ripple,
onEmojiStatusClick,
}) => {
const {
loadFullUser,
@ -159,6 +161,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
withEmojiStatus={!noEmojiStatus}
emojiStatusSize={emojiStatusSize}
isSavedMessages={isSavedMessages}
onEmojiStatusClick={onEmojiStatusClick}
/>
{customTitle && <span className="custom-title">{customTitle}</span>}
</div>
@ -171,6 +174,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
withEmojiStatus={!noEmojiStatus}
emojiStatusSize={emojiStatusSize}
isSavedMessages={isSavedMessages}
onEmojiStatusClick={onEmojiStatusClick}
/>
);
}

View File

@ -44,7 +44,7 @@
.custom-emoji {
--custom-emoji-size: 1.5rem;
color: var(--color-white);
color: var(--color-white) !important;
pointer-events: auto;
cursor: var(--custom-cursor, pointer);
}

View File

@ -138,10 +138,10 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
});
});
const handleClickPremium = useLastCallback(() => {
if (!user) return;
const handleStatusClick = useLastCallback(() => {
if (!userId) return;
openPremiumModal({ fromUserId: user.id });
openPremiumModal({ fromUserId: userId });
});
const selectPreviousMedia = useLastCallback(() => {
@ -334,7 +334,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
withEmojiStatus
emojiStatusSize={EMOJI_STATUS_SIZE}
isSavedMessages={isSavedMessages}
onEmojiStatusClick={handleClickPremium}
onEmojiStatusClick={handleStatusClick}
noLoopLimit
canCopyTitle
/>

View File

@ -11,7 +11,7 @@
}
.modal-header {
padding: 0.5rem 1rem;
padding: 0.5rem;
&.with-top-border {
/* stylelint-disable-next-line plugin/whole-pixel */
@ -24,13 +24,13 @@
text-align: center;
padding: 0 !important;
}
&.custom-emoji {
--emoji-size: 2.25rem;
&.custom-emoji .modal-content {
--emoji-size: 2.25rem;
.stickers {
padding: 0 0.5rem;
}
.stickers {
padding: 0 0.5rem;
}
}

View File

@ -27,4 +27,5 @@
align-self: center;
border-radius: 0.625rem;
background: var(--item-color, #000);
margin-left: 1rem;
}

View File

@ -1,3 +1,5 @@
@import '../../../styles/mixins';
.root {
--premium-gradient: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%);
--premium-feature-background: linear-gradient(65.85deg, #6C93FF -0.24%, #976FFF 53.99%, #DF69D1 110.53%);
@ -30,12 +32,14 @@
}
.main {
padding: 1rem;
padding: 1rem 0.5rem;
height: 100%;
overflow: auto;
overflow: scroll;
display: flex;
flex-direction: column;
align-items: center;
@include adapt-padding-to-scrollbar(0.5rem);
}
.logo {
@ -45,14 +49,23 @@
min-height: 6.25rem;
}
.status-emoji {
--custom-emoji-size: 8rem;
margin: 1rem;
cursor: var(--custom-cursor, pointer);
}
.header-text {
font-size: 1.5rem;
font-weight: 500;
text-align: center;
margin-inline: 0.5rem;
}
.description {
text-align: center;
margin-inline: 0.5rem;
margin-bottom: 2rem;
}
@ -119,6 +132,25 @@
padding: 1rem;
}
.stickerSetText {
font-size: 1.25rem;
}
.stickerSetLink {
--custom-emoji-size: 1.5rem;
color: var(--color-links);
cursor: var(--custom-cursor, pointer);
&:hover {
text-decoration: underline;
}
}
.stickerSetLinkIcon {
vertical-align: middle;
margin-inline-end: 0.25rem;
}
@media (max-width: 600px) {
.root :global(.modal-dialog) {

View File

@ -4,7 +4,9 @@ import React, {
import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { ApiPremiumPromo, ApiUser } from '../../../api/types';
import type {
ApiPremiumPromo, ApiSticker, ApiStickerSet, ApiUser,
} from '../../../api/types';
import type { GlobalState } from '../../../global/types';
import PremiumFeatureModal, {
@ -15,19 +17,24 @@ import PremiumFeatureModal, {
import { TME_LINK_PREFIX } from '../../../config';
import { formatCurrency } from '../../../util/formatCurrency';
import buildClassName from '../../../util/buildClassName';
import { selectTabState, selectIsCurrentUserPremium, selectUser } from '../../../global/selectors';
import {
selectTabState, selectIsCurrentUserPremium, selectUser, selectStickerSet,
} from '../../../global/selectors';
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
import { selectPremiumLimit } from '../../../global/selectors/limits';
import renderText from '../../common/helpers/renderText';
import { getUserFullName } from '../../../global/helpers';
import { REM } from '../../common/helpers/mediaDimensions';
import useLang from '../../../hooks/useLang';
import useSyncEffect from '../../../hooks/useSyncEffect';
import useLastCallback from '../../../hooks/useLastCallback';
import Modal from '../../ui/Modal';
import Button from '../../ui/Button';
import PremiumFeatureItem from './PremiumFeatureItem';
import Transition from '../../ui/Transition';
import CustomEmoji from '../../common/CustomEmoji';
import PremiumLogo from '../../../assets/premium/PremiumLogo.svg';
import PremiumLimits from '../../../assets/premium/PremiumLimits.svg';
@ -47,6 +54,7 @@ import PremiumTranslate from '../../../assets/premium/PremiumTranslate.svg';
import styles from './PremiumMainModal.module.scss';
const LIMIT_ACCOUNTS = 4;
const STATUS_EMOJI_SIZE = 8 * REM;
const PREMIUM_FEATURE_COLOR_ICONS: Record<string, string> = {
double_limits: PremiumLimits,
@ -73,6 +81,8 @@ type StateProps = {
promo?: ApiPremiumPromo;
isClosing?: boolean;
fromUser?: ApiUser;
fromUserStatusEmoji?: ApiSticker;
fromUserStatusSet?: ApiStickerSet;
toUser?: ApiUser;
initialSection?: string;
isPremium?: boolean;
@ -93,6 +103,8 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
isOpen,
currentUserId,
fromUser,
fromUserStatusEmoji,
fromUserStatusSet,
promo,
initialSection,
isPremium,
@ -113,7 +125,7 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
// eslint-disable-next-line no-null/no-null
const dialogRef = useRef<HTMLDivElement>(null);
const {
closePremiumModal, openInvoice, requestConfetti, openTelegramLink,
closePremiumModal, openInvoice, requestConfetti, openTelegramLink, loadStickers, openStickerSet,
} = getActions();
const lang = useLang();
@ -148,9 +160,9 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
}
}
function handleClick() {
const handleClick = useLastCallback(() => {
handleClickWithStartParam();
}
});
const showConfetti = useCallback(() => {
const dialog = dialogRef.current;
@ -185,7 +197,39 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
return premiumPromoOrder.filter((section) => PREMIUM_FEATURE_SECTIONS.includes(section));
}, [premiumPromoOrder]);
if (!promo) return undefined;
useEffect(() => {
if (!fromUserStatusEmoji || fromUserStatusSet) return;
loadStickers({
stickerSetInfo: fromUserStatusEmoji.stickerSetInfo,
});
}, [loadStickers, fromUserStatusEmoji, fromUserStatusSet]);
const handleOpenStatusSet = useLastCallback(() => {
if (!fromUserStatusSet) return;
openStickerSet({
stickerSetInfo: fromUserStatusSet,
});
});
const stickerSetTitle = useMemo(() => {
if (!fromUserStatusSet || !fromUser) return undefined;
const template = lang('lng_premium_emoji_status_title').replace('{user}', getUserFullName(fromUser)!);
const [first, second] = template.split('{link}');
const emoji = fromUserStatusSet.thumbCustomEmojiId ? (
<CustomEmoji className={styles.stickerSetLinkIcon} documentId={fromUserStatusSet.thumbCustomEmojiId} />
) : undefined;
const link = (
<span className={styles.stickerSetLink} onClick={handleOpenStatusSet}>
{emoji}{renderText(fromUserStatusSet.title)}
</span>
);
return [renderText(first), link, renderText(second)];
}, [fromUser, fromUserStatusSet, lang]);
if (!promo || (fromUserStatusEmoji && !fromUserStatusSet)) return undefined;
// TODO Support all subscription options
const month = promo.options.find((option) => option.months === 1)!;
@ -209,6 +253,10 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
: lang('TelegramPremiumUserGiftedPremiumDialogSubtitle');
}
if (fromUserStatusSet) {
return lang('TelegramPremiumUserStatusDialogSubtitle');
}
return fromUser
? lang('TelegramPremiumUserDialogSubtitle')
: lang(isPremium ? 'TelegramPremiumSubscribedSubtitle' : 'TelegramPremiumSubtitle');
@ -252,9 +300,19 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
>
<i className="icon icon-close" />
</Button>
<img className={styles.logo} src={PremiumLogo} alt="" />
<h2 className={styles.headerText}>
{renderText(getHeaderText(), ['simple_markdown', 'emoji'])}
{fromUserStatusEmoji ? (
<CustomEmoji
className={styles.statusEmoji}
onClick={handleOpenStatusSet}
documentId={fromUserStatusEmoji.id}
isBig
size={STATUS_EMOJI_SIZE}
/>
) : (
<img className={styles.logo} src={PremiumLogo} alt="" />
)}
<h2 className={buildClassName(styles.headerText, fromUserStatusSet && styles.stickerSetText)}>
{fromUserStatusSet ? stickerSetTitle : renderText(getHeaderText(), ['simple_markdown', 'emoji'])}
</h2>
<div className={styles.description}>
{renderText(getHeaderDescription(), ['simple_markdown', 'emoji'])}
@ -297,7 +355,6 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
</div>
{!isPremium && (
<div className={styles.footer}>
{/* eslint-disable-next-line react/jsx-no-bind */}
<Button className={styles.button} isShiny withPremiumGradient onClick={handleClick}>
{lang('SubscribeToPremium', formatCurrency(Number(month.amount), month.currency, lang.code))}
</Button>
@ -324,6 +381,13 @@ export default memo(withGlobal<OwnProps>((global): StateProps => {
const {
premiumModal,
} = selectTabState(global);
const fromUser = premiumModal?.fromUserId ? selectUser(global, premiumModal.fromUserId) : undefined;
const fromUserStatusEmoji = fromUser?.emojiStatus ? global.customEmojis.byId[fromUser.emojiStatus.documentId]
: undefined;
const fromUserStatusSet = fromUserStatusEmoji ? selectStickerSet(global, fromUserStatusEmoji.stickerSetInfo)
: undefined;
return {
currentUserId: global.currentUserId,
promo: premiumModal?.promo,
@ -331,7 +395,9 @@ export default memo(withGlobal<OwnProps>((global): StateProps => {
isSuccess: premiumModal?.isSuccess,
isGift: premiumModal?.isGift,
monthsAmount: premiumModal?.monthsAmount,
fromUser: premiumModal?.fromUserId ? selectUser(global, premiumModal.fromUserId) : undefined,
fromUser,
fromUserStatusEmoji,
fromUserStatusSet,
toUser: premiumModal?.toUserId ? selectUser(global, premiumModal.toUserId) : undefined,
initialSection: premiumModal?.initialSection,
isPremium: selectIsCurrentUserPremium(global),

View File

@ -149,6 +149,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
loadPinnedMessages,
toggleLeftColumn,
exitMessageSelectMode,
openPremiumModal,
} = getActions();
const lang = useLang();
@ -184,7 +185,12 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
const componentRef = useRef<HTMLDivElement>(null);
const shouldAnimateTools = useRef<boolean>(true);
const { handleClick: handleHeaderClick, handleMouseDown: handleHeaderMouseDown } = useFastClick(() => {
const {
handleClick: handleHeaderClick,
handleMouseDown: handleHeaderMouseDown,
} = useFastClick((e: React.MouseEvent<HTMLDivElement | HTMLButtonElement>) => {
if (e.type === 'mousedown' && (e.target as Element).closest('.title > .custom-emoji')) return;
openChatWithInfo({ id: chatId, threadId });
});
@ -214,6 +220,10 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
}, BACK_BUTTON_INACTIVE_TIME);
});
const handleStatusClick = useLastCallback(() => {
openPremiumModal({ fromUserId: chatId });
});
const handleBackClick = useLastCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
if (!isBackButtonActive.current) return;
@ -371,6 +381,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
withUpdatingStatus
emojiStatusSize={EMOJI_STATUS_SIZE}
noRtl
onEmojiStatusClick={handleStatusClick}
/>
) : (
<GroupChatInfo