Stars Gifting: Fixes for Stars Gifting (#4899)
This commit is contained in:
parent
1dfcbf1009
commit
e1504323f1
@ -8,7 +8,7 @@ import type {
|
|||||||
ApiGiveawayInfo,
|
ApiGiveawayInfo,
|
||||||
ApiInvoice, ApiLabeledPrice, ApiMyBoost, ApiPaymentCredentials,
|
ApiInvoice, ApiLabeledPrice, ApiMyBoost, ApiPaymentCredentials,
|
||||||
ApiPaymentForm, ApiPaymentSavedInfo, ApiPremiumGiftCodeOption, ApiPremiumPromo, ApiPremiumSubscriptionOption,
|
ApiPaymentForm, ApiPaymentSavedInfo, ApiPremiumGiftCodeOption, ApiPremiumPromo, ApiPremiumSubscriptionOption,
|
||||||
ApiReceipt, ApiStarsGiftOption,
|
ApiReceipt,
|
||||||
ApiStarsTransaction,
|
ApiStarsTransaction,
|
||||||
ApiStarsTransactionPeer,
|
ApiStarsTransactionPeer,
|
||||||
ApiStarTopupOption,
|
ApiStarTopupOption,
|
||||||
@ -386,7 +386,7 @@ export function buildApiPremiumGiftCodeOption(option: GramJs.PremiumGiftCodeOpti
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildApiStarsGiftOptions(option: GramJs.StarsGiftOption): ApiStarsGiftOption {
|
export function buildApiStarsGiftOptions(option: GramJs.StarsGiftOption): ApiStarTopupOption {
|
||||||
const {
|
const {
|
||||||
extended, stars, amount, currency,
|
extended, stars, amount, currency,
|
||||||
} = option;
|
} = option;
|
||||||
|
|||||||
@ -159,13 +159,6 @@ export interface ApiPremiumGiftCodeOption {
|
|||||||
amount: number;
|
amount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiStarsGiftOption {
|
|
||||||
isExtended?: true;
|
|
||||||
stars: number;
|
|
||||||
currency: string;
|
|
||||||
amount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ApiBoostsStatus = {
|
export type ApiBoostsStatus = {
|
||||||
level: number;
|
level: number;
|
||||||
currentLevelBoosts: number;
|
currentLevelBoosts: number;
|
||||||
|
|||||||
@ -135,6 +135,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.size-huge {
|
||||||
|
width: 6rem;
|
||||||
|
height: 6rem;
|
||||||
|
font-size: 3rem;
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.size-jumbo {
|
&.size-jumbo {
|
||||||
width: 7.5rem;
|
width: 7.5rem;
|
||||||
height: 7.5rem;
|
height: 7.5rem;
|
||||||
|
|||||||
@ -45,7 +45,8 @@ import './Avatar.scss';
|
|||||||
|
|
||||||
const LOOP_COUNT = 3;
|
const LOOP_COUNT = 3;
|
||||||
|
|
||||||
export type AvatarSize = 'micro' | 'tiny' | 'mini' | 'small' | 'small-mobile' | 'medium' | 'large' | 'giant' | 'jumbo';
|
export type AvatarSize =
|
||||||
|
'micro' | 'tiny' | 'mini' | 'small' | 'small-mobile' | 'medium' | 'large' | 'giant' | 'huge' | 'jumbo';
|
||||||
|
|
||||||
const cn = createClassNameBuilder('Avatar');
|
const cn = createClassNameBuilder('Avatar');
|
||||||
cn.media = cn('media');
|
cn.media = cn('media');
|
||||||
|
|||||||
@ -36,6 +36,10 @@
|
|||||||
--size: 3.375rem;
|
--size: 3.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.size-huge {
|
||||||
|
--size: 6.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.size-jumbo {
|
.size-jumbo {
|
||||||
--size: 7.5rem;
|
--size: 7.5rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,6 +37,7 @@ const SIZES: Record<AvatarSize, number> = {
|
|||||||
medium: 2.875 * REM,
|
medium: 2.875 * REM,
|
||||||
large: 3.5 * REM,
|
large: 3.5 * REM,
|
||||||
giant: 5.125 * REM,
|
giant: 5.125 * REM,
|
||||||
|
huge: 6.125 * REM,
|
||||||
jumbo: 7.625 * REM,
|
jumbo: 7.625 * REM,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
|||||||
import type { LangFn } from '../../../hooks/useOldLang';
|
import type { LangFn } from '../../../hooks/useOldLang';
|
||||||
import type { TextPart } from '../../../types';
|
import type { TextPart } from '../../../types';
|
||||||
|
|
||||||
|
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
||||||
import {
|
import {
|
||||||
getChatTitle,
|
getChatTitle,
|
||||||
getExpiredMessageDescription,
|
getExpiredMessageDescription,
|
||||||
@ -111,7 +112,9 @@ export function renderActionMessageText(
|
|||||||
actionOriginChat ? (
|
actionOriginChat ? (
|
||||||
renderChatContent(lang, actionOriginChat, noLinks) || NBSP
|
renderChatContent(lang, actionOriginChat, noLinks) || NBSP
|
||||||
) : actionOriginUser ? (
|
) : actionOriginUser ? (
|
||||||
renderUserContent(actionOriginUser, noLinks) || NBSP
|
actionOriginUser.id === SERVICE_NOTIFICATIONS_USER_ID
|
||||||
|
? lang('StarsTransactionUnknown')
|
||||||
|
: renderUserContent(actionOriginUser, noLinks) || NBSP
|
||||||
) : 'User',
|
) : 'User',
|
||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
|
|||||||
@ -28,6 +28,7 @@ const PickerModal = ({
|
|||||||
...modalProps
|
...modalProps
|
||||||
}: OwnProps) => {
|
}: OwnProps) => {
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
|
const hasOnClickHandler = Boolean(onConfirm || modalProps.onClose);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@ -43,17 +44,19 @@ const PickerModal = ({
|
|||||||
headerClassName={buildClassName(styles.header, modalProps.headerClassName)}
|
headerClassName={buildClassName(styles.header, modalProps.headerClassName)}
|
||||||
>
|
>
|
||||||
{modalProps.children}
|
{modalProps.children}
|
||||||
<div className={styles.buttonWrapper}>
|
{hasOnClickHandler && (
|
||||||
<Button
|
<div className={styles.buttonWrapper}>
|
||||||
withPremiumGradient={withPremiumGradient}
|
<Button
|
||||||
onClick={onConfirm || modalProps.onClose}
|
withPremiumGradient={withPremiumGradient}
|
||||||
color="primary"
|
onClick={onConfirm || modalProps.onClose}
|
||||||
size="smaller"
|
color="primary"
|
||||||
disabled={isConfirmDisabled}
|
size="smaller"
|
||||||
>
|
disabled={isConfirmDisabled}
|
||||||
{confirmButtonText || lang('Confirm')}
|
>
|
||||||
</Button>
|
{confirmButtonText || lang('Confirm')}
|
||||||
</div>
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -86,6 +86,7 @@ const PremiumGiftingPickerModal: FC<OwnProps & StateProps> = ({
|
|||||||
onConfirm={handleSendIdList}
|
onConfirm={handleSendIdList}
|
||||||
onEnter={handleSendIdList}
|
onEnter={handleSendIdList}
|
||||||
withPremiumGradient
|
withPremiumGradient
|
||||||
|
isConfirmDisabled={!selectedUserIds?.length}
|
||||||
>
|
>
|
||||||
<PeerPicker
|
<PeerPicker
|
||||||
className={styles.picker}
|
className={styles.picker}
|
||||||
|
|||||||
@ -30,7 +30,39 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.headerInfo {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin: 1rem;
|
||||||
|
width: 6.25rem;
|
||||||
|
height: 6.25rem;
|
||||||
|
min-height: 6.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoBackground {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.75rem;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
height: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
text-align: center;
|
||||||
|
margin-inline: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,41 +99,24 @@
|
|||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatars {
|
.avatar {
|
||||||
display: flex;
|
margin: 2rem;
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
margin: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.center {
|
.center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 0.5rem;
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.boostIcon {
|
|
||||||
color: var(--color-primary);
|
|
||||||
vertical-align: middle;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.moreOptions {
|
|
||||||
grid-column: 1/-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondaryInfo {
|
.secondaryInfo {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
padding: 0.5rem 1rem;
|
padding: 1rem;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 450px) {
|
||||||
|
.modalDialog :global(.modal-dialog) {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -6,9 +6,10 @@ import React, {
|
|||||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiStarsGiftOption, ApiStarTopupOption, ApiUser,
|
ApiStarTopupOption, ApiUser,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
|
||||||
|
import { getSenderTitle } from '../../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectTabState, selectUser,
|
selectTabState, selectUser,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
@ -27,13 +28,16 @@ import Modal from '../../ui/Modal';
|
|||||||
|
|
||||||
import styles from './StarsGiftModal.module.scss';
|
import styles from './StarsGiftModal.module.scss';
|
||||||
|
|
||||||
|
import StarLogo from '../../../assets/icons/StarLogo.svg';
|
||||||
|
import StarsBackground from '../../../assets/stars-bg.png';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
isCompleted?: boolean;
|
isCompleted?: boolean;
|
||||||
starsGiftOptions?: ApiStarsGiftOption[] | undefined;
|
starsGiftOptions?: ApiStarTopupOption[] | undefined;
|
||||||
forUserId?: string;
|
forUserId?: string;
|
||||||
user?: ApiUser;
|
user?: ApiUser;
|
||||||
};
|
};
|
||||||
@ -88,13 +92,22 @@ const StarsGiftModal: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const handleClick = useLastCallback((option: ApiStarTopupOption) => {
|
const handleClick = useLastCallback((option: ApiStarTopupOption) => {
|
||||||
setSelectedOption(option);
|
setSelectedOption(option);
|
||||||
openInvoice({
|
if (user) {
|
||||||
type: 'starsgift',
|
openInvoice({
|
||||||
userId: forUserId!,
|
type: 'starsgift',
|
||||||
stars: option.stars,
|
userId: forUserId!,
|
||||||
currency: option.currency,
|
stars: option.stars,
|
||||||
amount: option.amount,
|
currency: option.currency,
|
||||||
});
|
amount: option.amount,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
openInvoice({
|
||||||
|
type: 'stars',
|
||||||
|
stars: option.stars,
|
||||||
|
currency: option.currency,
|
||||||
|
amount: option.amount,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleScroll(e: React.UIEvent<HTMLDivElement>) {
|
function handleScroll(e: React.UIEvent<HTMLDivElement>) {
|
||||||
@ -105,11 +118,12 @@ const StarsGiftModal: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
function renderGiftTitle() {
|
function renderGiftTitle() {
|
||||||
if (isCompleted) {
|
if (isCompleted) {
|
||||||
return renderText(oldLang('Notification.StarsGift.SentYou',
|
return user ? renderText(oldLang('Notification.StarsGift.SentYou',
|
||||||
formatCurrencyAsString(selectedOption!.amount, selectedOption!.currency, oldLang.code)), ['simple_markdown']);
|
formatCurrencyAsString(selectedOption!.amount, selectedOption!.currency, oldLang.code)), ['simple_markdown'])
|
||||||
|
: renderText(oldLang('StarsAcquiredInfo', selectedOption?.stars), ['simple_markdown']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return oldLang('GiftStarsTitle');
|
return user ? oldLang('GiftStarsTitle') : oldLang('Star.List.GetStars');
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderStarOptionList() {
|
function renderStarOptionList() {
|
||||||
@ -154,28 +168,40 @@ const StarsGiftModal: FC<OwnProps & StateProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<div className={buildClassName(styles.header, isHeaderHidden && styles.hiddenHeader)}>
|
<div className={buildClassName(styles.header, isHeaderHidden && styles.hiddenHeader)}>
|
||||||
<h2 className={styles.starHeaderText}>
|
<h2 className={styles.starHeaderText}>
|
||||||
{oldLang('GiftStarsTitle')}
|
{user ? oldLang('GiftStarsTitle') : oldLang('Star.List.GetStars')}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.avatars}>
|
<div className={styles.headerInfo}>
|
||||||
<Avatar
|
{user ? (
|
||||||
size="large"
|
<>
|
||||||
peer={user}
|
<Avatar
|
||||||
/>
|
size="huge"
|
||||||
|
peer={user}
|
||||||
|
className={styles.avatar}
|
||||||
|
/>
|
||||||
|
<img className={styles.logoBackground} src={StarsBackground} alt="" draggable={false} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<img className={styles.logo} src={StarLogo} alt="" draggable={false} />
|
||||||
|
<img className={styles.logoBackground} src={StarsBackground} alt="" draggable={false} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<h2 className={buildClassName(styles.headerText, styles.center)}>
|
<h2 className={buildClassName(styles.headerText, styles.center)}>
|
||||||
{renderGiftTitle()}
|
{renderGiftTitle()}
|
||||||
</h2>
|
</h2>
|
||||||
{!isCompleted && (
|
<p className={styles.description}>
|
||||||
<>
|
{user ? renderText(
|
||||||
<div className={buildClassName(styles.section, styles.options)}>
|
oldLang('ActionGiftStarsSubtitle', getSenderTitle(oldLang, user)), ['simple_markdown'],
|
||||||
{renderStarOptionList()}
|
) : oldLang('Stars.Purchase.GetStarsInfo')}
|
||||||
</div>
|
</p>
|
||||||
<div className={styles.secondaryInfo}>
|
<div className={styles.section}>
|
||||||
{bottomText}
|
{renderStarOptionList()}
|
||||||
</div>
|
<div className={styles.secondaryInfo}>
|
||||||
</>
|
{bottomText}
|
||||||
)}
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useMemo,
|
memo, useMemo,
|
||||||
useRef,
|
|
||||||
useState,
|
useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||||
@ -10,17 +9,14 @@ import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
|||||||
import {
|
import {
|
||||||
filterUsersByName, isDeletedUser, isUserBot,
|
filterUsersByName, isDeletedUser, isUserBot,
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
|
||||||
import { unique } from '../../../util/iteratees';
|
import { unique } from '../../../util/iteratees';
|
||||||
import sortChatIds from '../../common/helpers/sortChatIds';
|
import sortChatIds from '../../common/helpers/sortChatIds';
|
||||||
|
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import Icon from '../../common/icons/Icon';
|
|
||||||
import PeerPicker from '../../common/pickers/PeerPicker';
|
import PeerPicker from '../../common/pickers/PeerPicker';
|
||||||
import Button from '../../ui/Button';
|
import PickerModal from '../../common/pickers/PickerModal';
|
||||||
import Modal from '../../ui/Modal';
|
|
||||||
|
|
||||||
import styles from './StarsGiftingPickerModal.module.scss';
|
import styles from './StarsGiftingPickerModal.module.scss';
|
||||||
|
|
||||||
@ -42,8 +38,6 @@ const StarsGiftingPickerModal: FC<OwnProps & StateProps> = ({
|
|||||||
archivedListIds,
|
archivedListIds,
|
||||||
userIds,
|
userIds,
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
|
||||||
const dialogRef = useRef<HTMLDivElement>(null);
|
|
||||||
const { closeStarsGiftingModal, openStarsGiftModal } = getActions();
|
const { closeStarsGiftingModal, openStarsGiftModal } = getActions();
|
||||||
|
|
||||||
const oldLang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
@ -79,48 +73,30 @@ const StarsGiftingPickerModal: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function renderHeaderText() {
|
|
||||||
return (
|
|
||||||
<div className={styles.filter} dir={oldLang.isRtl ? 'rtl' : undefined}>
|
|
||||||
<Button
|
|
||||||
round
|
|
||||||
size="smaller"
|
|
||||||
color="translucent"
|
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
|
||||||
onClick={() => closeStarsGiftingModal()}
|
|
||||||
ariaLabel={oldLang('Close')}
|
|
||||||
>
|
|
||||||
<Icon name="close" />
|
|
||||||
</Button>
|
|
||||||
<h3 className={buildClassName(styles.title, 'ml-2')}>{oldLang('GiftStarsTitle')}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<PickerModal
|
||||||
className={styles.root}
|
className={styles.root}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={closeStarsGiftingModal}
|
onClose={closeStarsGiftingModal}
|
||||||
onEnter={handleSelectedUserIdsChange}
|
title={oldLang('GiftStarsTitle')}
|
||||||
dialogRef={dialogRef}
|
hasCloseButton
|
||||||
|
shouldAdaptToSearch
|
||||||
|
withFixedHeight
|
||||||
|
confirmButtonText={oldLang('Continue')}
|
||||||
|
onEnter={closeStarsGiftingModal}
|
||||||
>
|
>
|
||||||
<div className={buildClassName(styles.main, 'custom-scroll')}>
|
<PeerPicker
|
||||||
{renderHeaderText()}
|
className={styles.picker}
|
||||||
<PeerPicker
|
itemIds={displayedUserIds}
|
||||||
className={styles.picker}
|
filterValue={searchQuery}
|
||||||
itemIds={displayedUserIds}
|
filterPlaceholder={oldLang('Search')}
|
||||||
filterValue={searchQuery}
|
onFilterChange={setSearchQuery}
|
||||||
filterPlaceholder={oldLang('Search')}
|
isSearchable
|
||||||
onFilterChange={setSearchQuery}
|
withDefaultPadding
|
||||||
isSearchable
|
withStatus
|
||||||
withDefaultPadding
|
onSelectedIdChange={handleSelectedUserIdsChange}
|
||||||
withStatus
|
/>
|
||||||
onSelectedIdChange={handleSelectedUserIdsChange}
|
</PickerModal>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -21,12 +21,14 @@ import {
|
|||||||
selectChat,
|
selectChat,
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectGiftStickerForDuration,
|
selectGiftStickerForDuration,
|
||||||
|
selectGiftStickerForStars,
|
||||||
selectIsMessageFocused,
|
selectIsMessageFocused,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectTopicFromMessage,
|
selectTopicFromMessage,
|
||||||
selectUser,
|
selectUser,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
import { formatInteger } from '../../util/textFormat';
|
||||||
import { renderActionMessageText } from '../common/helpers/renderActionMessageText';
|
import { renderActionMessageText } from '../common/helpers/renderActionMessageText';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
||||||
@ -72,6 +74,7 @@ type StateProps = {
|
|||||||
focusDirection?: FocusDirection;
|
focusDirection?: FocusDirection;
|
||||||
noFocusHighlight?: boolean;
|
noFocusHighlight?: boolean;
|
||||||
premiumGiftSticker?: ApiSticker;
|
premiumGiftSticker?: ApiSticker;
|
||||||
|
starGiftSticker?: ApiSticker;
|
||||||
canPlayAnimatedEmojis?: boolean;
|
canPlayAnimatedEmojis?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -93,6 +96,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
focusDirection,
|
focusDirection,
|
||||||
noFocusHighlight,
|
noFocusHighlight,
|
||||||
premiumGiftSticker,
|
premiumGiftSticker,
|
||||||
|
starGiftSticker,
|
||||||
isInsideTopic,
|
isInsideTopic,
|
||||||
topic,
|
topic,
|
||||||
memoFirstUnreadIdRef,
|
memoFirstUnreadIdRef,
|
||||||
@ -315,15 +319,16 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
>
|
>
|
||||||
<AnimatedIconFromSticker
|
<AnimatedIconFromSticker
|
||||||
key={message.id}
|
key={message.id}
|
||||||
sticker={premiumGiftSticker}
|
sticker={starGiftSticker}
|
||||||
play={canPlayAnimatedEmojis}
|
play={canPlayAnimatedEmojis}
|
||||||
noLoop
|
noLoop
|
||||||
nonInteractive
|
nonInteractive
|
||||||
/>
|
/>
|
||||||
<strong>
|
<div className="action-message-stars-balance">
|
||||||
{oldLang('Stars', message.content.action!.stars)}
|
{formatInteger(message.content.action!.stars!)}
|
||||||
</strong>
|
<strong>{oldLang('Stars')}</strong>
|
||||||
<span className="action-message-subtitle">
|
</div>
|
||||||
|
<span className="action-message-stars-subtitle">
|
||||||
{renderText(
|
{renderText(
|
||||||
oldLang(!message.isOutgoing
|
oldLang(!message.isOutgoing
|
||||||
? 'ActionGiftStarsSubtitleYou' : 'ActionGiftStarsSubtitle', getChatTitle(oldLang, targetChat!)),
|
? 'ActionGiftStarsSubtitleYou' : 'ActionGiftStarsSubtitle', getChatTitle(oldLang, targetChat!)),
|
||||||
@ -406,6 +411,10 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const giftDuration = content.action?.months;
|
const giftDuration = content.action?.months;
|
||||||
const premiumGiftSticker = selectGiftStickerForDuration(global, giftDuration);
|
const premiumGiftSticker = selectGiftStickerForDuration(global, giftDuration);
|
||||||
|
|
||||||
|
const starCount = content.action?.stars;
|
||||||
|
const starGiftSticker = selectGiftStickerForStars(global, starCount);
|
||||||
|
|
||||||
const topic = selectTopicFromMessage(global, message);
|
const topic = selectTopicFromMessage(global, message);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -417,6 +426,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
targetMessage,
|
targetMessage,
|
||||||
isFocused,
|
isFocused,
|
||||||
premiumGiftSticker,
|
premiumGiftSticker,
|
||||||
|
starGiftSticker,
|
||||||
topic,
|
topic,
|
||||||
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
|
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
|
||||||
...(isFocused && {
|
...(isFocused && {
|
||||||
|
|||||||
@ -273,7 +273,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action-message-gift-code {
|
.action-message-gift-code {
|
||||||
width: 20rem;
|
width: 12rem;
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,12 +282,25 @@
|
|||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-message-stars-balance {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.action-message-subtitle {
|
.action-message-subtitle {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
text-wrap: balance;
|
text-wrap: balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-message-stars-subtitle {
|
||||||
|
font-weight: normal;
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
|
||||||
.action-message-suggested-avatar {
|
.action-message-suggested-avatar {
|
||||||
max-width: 16rem;
|
max-width: 16rem;
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
|
|||||||
@ -30,6 +30,7 @@ type OwnProps = {
|
|||||||
headerAvatarPeer?: ApiPeer | CustomPeer;
|
headerAvatarPeer?: ApiPeer | CustomPeer;
|
||||||
headerAvatarWebPhoto?: ApiWebDocument;
|
headerAvatarWebPhoto?: ApiWebDocument;
|
||||||
noHeaderImage?: boolean;
|
noHeaderImage?: boolean;
|
||||||
|
isGift?: boolean;
|
||||||
header?: TeactNode;
|
header?: TeactNode;
|
||||||
footer?: TeactNode;
|
footer?: TeactNode;
|
||||||
buttonText?: string;
|
buttonText?: string;
|
||||||
@ -47,6 +48,7 @@ const TableInfoModal = ({
|
|||||||
headerAvatarPeer,
|
headerAvatarPeer,
|
||||||
headerAvatarWebPhoto,
|
headerAvatarWebPhoto,
|
||||||
noHeaderImage,
|
noHeaderImage,
|
||||||
|
isGift,
|
||||||
header,
|
header,
|
||||||
footer,
|
footer,
|
||||||
buttonText,
|
buttonText,
|
||||||
@ -73,7 +75,7 @@ const TableInfoModal = ({
|
|||||||
contentClassName={styles.content}
|
contentClassName={styles.content}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
>
|
>
|
||||||
{!noHeaderImage && (
|
{!isGift && !noHeaderImage && (
|
||||||
withAvatar ? (
|
withAvatar ? (
|
||||||
<Avatar peer={headerAvatarPeer} webPhoto={headerAvatarWebPhoto} size="jumbo" className={styles.avatar} />
|
<Avatar peer={headerAvatarPeer} webPhoto={headerAvatarWebPhoto} size="jumbo" className={styles.avatar} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -1,5 +1,12 @@
|
|||||||
@use '../../../styles/mixins';
|
@use '../../../styles/mixins';
|
||||||
|
|
||||||
|
.options {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.option {
|
.option {
|
||||||
--_background-color: var(--color-background-secondary);
|
--_background-color: var(--color-background-secondary);
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -60,3 +67,9 @@
|
|||||||
margin-inline-start: 0.25rem;
|
margin-inline-start: 0.25rem;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 450px) {
|
||||||
|
.optionTop {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -73,7 +73,7 @@ const StarTopupOptionList: FC<OwnProps> = ({
|
|||||||
}, [areOptionsExtended, options, starsNeeded]);
|
}, [areOptionsExtended, options, starsNeeded]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={styles.options}>
|
||||||
{renderingOptions?.map(({ option, starsCount, isWide }) => {
|
{renderingOptions?.map(({ option, starsCount, isWide }) => {
|
||||||
const length = renderingOptions?.length;
|
const length = renderingOptions?.length;
|
||||||
const isOdd = length % 2 === 0;
|
const isOdd = length % 2 === 0;
|
||||||
@ -103,7 +103,7 @@ const StarTopupOptionList: FC<OwnProps> = ({
|
|||||||
<Icon className={styles.iconDown} name="down" />
|
<Icon className={styles.iconDown} name="down" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -136,23 +136,10 @@
|
|||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 0.5rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.optionFullWidth {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.starButton {
|
.starButton {
|
||||||
grid-column: 1/-1;
|
grid-column: 1/-1;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.paymentContent {
|
.paymentContent {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import React, {
|
|||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiStarTopupOption, ApiUser } from '../../../api/types';
|
import type { ApiUser } from '../../../api/types';
|
||||||
import type { GlobalState, TabState } from '../../../global/types';
|
import type { GlobalState, TabState } from '../../../global/types';
|
||||||
|
|
||||||
import { getUserFullName } from '../../../global/helpers';
|
import { getUserFullName } from '../../../global/helpers';
|
||||||
@ -24,7 +24,6 @@ import Modal from '../../ui/Modal';
|
|||||||
import TabList, { type TabWithProperties } from '../../ui/TabList';
|
import TabList, { type TabWithProperties } from '../../ui/TabList';
|
||||||
import Transition from '../../ui/Transition';
|
import Transition from '../../ui/Transition';
|
||||||
import BalanceBlock from './BalanceBlock';
|
import BalanceBlock from './BalanceBlock';
|
||||||
import StarTopupOptionList from './StarTopupOptionList';
|
|
||||||
import TransactionItem from './transaction/StarsTransactionItem';
|
import TransactionItem from './transaction/StarsTransactionItem';
|
||||||
|
|
||||||
import styles from './StarsBalanceModal.module.scss';
|
import styles from './StarsBalanceModal.module.scss';
|
||||||
@ -53,10 +52,10 @@ const StarsBalanceModal = ({
|
|||||||
modal, starsBalanceState, originPaymentBot, canBuyPremium,
|
modal, starsBalanceState, originPaymentBot, canBuyPremium,
|
||||||
}: OwnProps & StateProps) => {
|
}: OwnProps & StateProps) => {
|
||||||
const {
|
const {
|
||||||
closeStarsBalanceModal, loadStarsTransactions, openInvoice, openStarsGiftingModal,
|
closeStarsBalanceModal, loadStarsTransactions, openStarsGiftingModal, openStarsGiftModal,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const { balance, history, topupOptions } = starsBalanceState || {};
|
const { balance, history } = starsBalanceState || {};
|
||||||
|
|
||||||
const oldLang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
@ -96,36 +95,20 @@ const StarsBalanceModal = ({
|
|||||||
setHeaderHidden(scrollTop <= 150);
|
setHeaderHidden(scrollTop <= 150);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClick = useLastCallback((option: ApiStarTopupOption) => {
|
|
||||||
openInvoice({
|
|
||||||
type: 'stars',
|
|
||||||
stars: option.stars,
|
|
||||||
currency: option.currency,
|
|
||||||
amount: option.amount,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function renderStarOptionList() {
|
|
||||||
return (
|
|
||||||
<StarTopupOptionList
|
|
||||||
isActive={isOpen}
|
|
||||||
options={topupOptions}
|
|
||||||
starsNeeded={starsNeeded}
|
|
||||||
onClick={handleClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleLoadMore = useLastCallback(() => {
|
const handleLoadMore = useLastCallback(() => {
|
||||||
loadStarsTransactions({
|
loadStarsTransactions({
|
||||||
type: TRANSACTION_TYPES[selectedTabIndex],
|
type: TRANSACTION_TYPES[selectedTabIndex],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const openPremiumGiftingModalHandler = useLastCallback(() => {
|
const openStarsGiftingModalHandler = useLastCallback(() => {
|
||||||
openStarsGiftingModal({});
|
openStarsGiftingModal({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const openStarsInfoModalHandler = useLastCallback(() => {
|
||||||
|
openStarsGiftModal({});
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal className={styles.root} isOpen={isOpen} onClose={closeStarsBalanceModal}>
|
<Modal className={styles.root} isOpen={isOpen} onClose={closeStarsBalanceModal}>
|
||||||
<div className={buildClassName(styles.main, 'custom-scroll')} onScroll={handleScroll}>
|
<div className={buildClassName(styles.main, 'custom-scroll')} onScroll={handleScroll}>
|
||||||
@ -158,19 +141,24 @@ const StarsBalanceModal = ({
|
|||||||
['simple_markdown', 'emoji'],
|
['simple_markdown', 'emoji'],
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.options}>
|
{canBuyPremium && (
|
||||||
{renderStarOptionList()}
|
<Button
|
||||||
{canBuyPremium && (
|
className={styles.starButton}
|
||||||
<Button
|
onClick={openStarsInfoModalHandler}
|
||||||
className={buildClassName(styles.starButton, 'settings-main-menu-star')}
|
>
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
{oldLang('Star.List.BuyMoreStars')}
|
||||||
onClick={openPremiumGiftingModalHandler}
|
</Button>
|
||||||
>
|
)}
|
||||||
<StarIcon className="icon" type="gold" size="big" />
|
{canBuyPremium && (
|
||||||
{oldLang('TelegramStarsGift')}
|
<Button
|
||||||
</Button>
|
className={buildClassName(styles.starButton, 'settings-main-menu-star')}
|
||||||
)}
|
color="translucent"
|
||||||
</div>
|
onClick={openStarsGiftingModalHandler}
|
||||||
|
>
|
||||||
|
<StarIcon className="icon" type="gold" size="big" />
|
||||||
|
{oldLang('TelegramStarsGift')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.secondaryInfo}>
|
<div className={styles.secondaryInfo}>
|
||||||
{tosText}
|
{tosText}
|
||||||
|
|||||||
@ -2,6 +2,10 @@
|
|||||||
z-index: calc(var(--z-media-viewer) - 1);
|
z-index: calc(var(--z-media-viewer) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal :global(.modal-dialog) {
|
||||||
|
max-width: 26rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
.positive {
|
.positive {
|
||||||
color: var(--color-success);
|
color: var(--color-success);
|
||||||
}
|
}
|
||||||
@ -19,10 +23,14 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.starsHeader {
|
||||||
|
gap: normal;
|
||||||
|
}
|
||||||
|
|
||||||
.amount {
|
.amount {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
font-size: 1.25rem;
|
font-size: 1rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1.325;
|
line-height: 1.325;
|
||||||
}
|
}
|
||||||
@ -44,6 +52,7 @@
|
|||||||
.footer {
|
.footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-block: 0.5rem;
|
margin-block: 0.5rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.starsBackground {
|
.starsBackground {
|
||||||
@ -67,3 +76,12 @@
|
|||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
cursor: var(--custom-cursor, pointer);
|
cursor: var(--custom-cursor, pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.starTitle {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|||||||
@ -4,22 +4,29 @@ import { getActions, withGlobal } from '../../../../global';
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiStarsTransactionPeer,
|
ApiStarsTransactionPeer, ApiSticker, ApiUser,
|
||||||
} from '../../../../api/types';
|
} from '../../../../api/types';
|
||||||
import type { TabState } from '../../../../global/types';
|
import type { TabState } from '../../../../global/types';
|
||||||
import { MediaViewerOrigin } from '../../../../types';
|
import { MediaViewerOrigin } from '../../../../types';
|
||||||
|
|
||||||
import { getMessageLink } from '../../../../global/helpers';
|
import { getMessageLink, getUserFullName } from '../../../../global/helpers';
|
||||||
import { buildStarsTransactionCustomPeer, formatStarsTransactionAmount } from '../../../../global/helpers/payments';
|
import { buildStarsTransactionCustomPeer, formatStarsTransactionAmount } from '../../../../global/helpers/payments';
|
||||||
import { selectPeer } from '../../../../global/selectors';
|
import {
|
||||||
|
selectCanPlayAnimatedEmojis,
|
||||||
|
selectGiftStickerForStars,
|
||||||
|
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';
|
||||||
import { formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
import { formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
||||||
|
import renderText from '../../../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../../hooks/useOldLang';
|
import useOldLang from '../../../../hooks/useOldLang';
|
||||||
import usePrevious from '../../../../hooks/usePrevious';
|
import usePrevious from '../../../../hooks/usePrevious';
|
||||||
|
|
||||||
|
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
|
||||||
import Icon from '../../../common/icons/Icon';
|
import Icon from '../../../common/icons/Icon';
|
||||||
import StarIcon from '../../../common/icons/StarIcon';
|
import StarIcon from '../../../common/icons/StarIcon';
|
||||||
import SafeLink from '../../../common/SafeLink';
|
import SafeLink from '../../../common/SafeLink';
|
||||||
@ -36,14 +43,19 @@ export type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
peer?: ApiPeer;
|
peer?: ApiPeer;
|
||||||
|
user?: ApiUser;
|
||||||
|
canPlayAnimatedEmojis?: boolean;
|
||||||
|
starGiftSticker?: ApiSticker;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||||
modal, peer,
|
modal, peer, user, canPlayAnimatedEmojis, starGiftSticker,
|
||||||
}) => {
|
}) => {
|
||||||
const { showNotification, openMediaViewer, closeStarsTransactionModal } = getActions();
|
const { showNotification, openMediaViewer, closeStarsTransactionModal } = getActions();
|
||||||
const lang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
|
const lang = useLang();
|
||||||
const { transaction } = modal || {};
|
const { transaction } = modal || {};
|
||||||
|
const isGift = transaction?.isGift;
|
||||||
|
|
||||||
const handleOpenMedia = useLastCallback(() => {
|
const handleOpenMedia = useLastCallback(() => {
|
||||||
const media = transaction?.extendedMedia;
|
const media = transaction?.extendedMedia;
|
||||||
@ -55,6 +67,60 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const animatedStickerData = useMemo(() => {
|
||||||
|
if (!transaction) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedIconFromSticker
|
||||||
|
key={transaction.id}
|
||||||
|
sticker={starGiftSticker}
|
||||||
|
play={canPlayAnimatedEmojis}
|
||||||
|
noLoop
|
||||||
|
nonInteractive
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [canPlayAnimatedEmojis, starGiftSticker, transaction]);
|
||||||
|
|
||||||
|
const giftEntryAboutText = useMemo(() => {
|
||||||
|
const subtitleText = oldLang('lng_credits_box_history_entry_gift_in_about');
|
||||||
|
const subtitleTextParts = subtitleText.split('{link}');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{subtitleTextParts[0]}
|
||||||
|
<SafeLink
|
||||||
|
url={oldLang('lng_credits_box_history_entry_gift_about_url')}
|
||||||
|
text={oldLang('GiftStarsSubtitleLinkName')}
|
||||||
|
>
|
||||||
|
{renderText(oldLang('GiftStarsSubtitleLinkName'), ['simple_markdown'])}
|
||||||
|
</SafeLink>
|
||||||
|
{subtitleTextParts[1]}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}, [oldLang]);
|
||||||
|
|
||||||
|
const giftOutAboutText = useMemo(() => {
|
||||||
|
return lang(
|
||||||
|
'CreditsBoxHistoryEntryGiftOutAbout',
|
||||||
|
{
|
||||||
|
user: <strong>{user ? getUserFullName(user) : ''}</strong>,
|
||||||
|
link: (
|
||||||
|
<SafeLink
|
||||||
|
url={oldLang('lng_credits_box_history_entry_gift_about_url')}
|
||||||
|
text={oldLang('GiftStarsSubtitleLinkName')}
|
||||||
|
>
|
||||||
|
{renderText(oldLang('GiftStarsSubtitleLinkName'), ['simple_markdown'])}
|
||||||
|
</SafeLink>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
withNodes: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}, [lang, user, oldLang]);
|
||||||
|
|
||||||
const starModalData = useMemo(() => {
|
const starModalData = useMemo(() => {
|
||||||
if (!transaction) {
|
if (!transaction) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -64,9 +130,9 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
&& buildStarsTransactionCustomPeer(transaction.peer)) || undefined;
|
&& buildStarsTransactionCustomPeer(transaction.peer)) || undefined;
|
||||||
|
|
||||||
const peerId = transaction.peer?.type === 'peer' ? transaction.peer.id : undefined;
|
const peerId = transaction.peer?.type === 'peer' ? transaction.peer.id : undefined;
|
||||||
const toName = transaction.peer && lang(getStarsPeerTitleKey(transaction.peer));
|
const toName = transaction.peer && oldLang(getStarsPeerTitleKey(transaction.peer));
|
||||||
|
|
||||||
const title = transaction.title || (customPeer ? lang(customPeer.titleKey) : undefined);
|
const title = transaction.title || (customPeer ? oldLang(customPeer.titleKey) : undefined);
|
||||||
|
|
||||||
const messageLink = peer && transaction.messageId
|
const messageLink = peer && transaction.messageId
|
||||||
? getMessageLink(peer, undefined, transaction.messageId) : undefined;
|
? getMessageLink(peer, undefined, transaction.messageId) : undefined;
|
||||||
@ -77,14 +143,14 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
const areAllPhotos = media?.every((m) => !m.video);
|
const areAllPhotos = media?.every((m) => !m.video);
|
||||||
const areAllVideos = media?.every((m) => !m.photo);
|
const areAllVideos = media?.every((m) => !m.photo);
|
||||||
|
|
||||||
const mediaText = areAllPhotos ? lang('Stars.Transfer.Photos', mediaAmount)
|
const mediaText = areAllPhotos ? oldLang('Stars.Transfer.Photos', mediaAmount)
|
||||||
: areAllVideos ? lang('Stars.Transfer.Videos', mediaAmount)
|
: areAllVideos ? oldLang('Stars.Transfer.Videos', mediaAmount)
|
||||||
: lang('Media', mediaAmount);
|
: oldLang('Media', mediaAmount);
|
||||||
|
|
||||||
const description = transaction.description || (media ? mediaText : undefined);
|
const description = transaction.description || (media ? mediaText : undefined);
|
||||||
|
|
||||||
const header = (
|
const header = (
|
||||||
<div className={styles.header}>
|
<div className={buildClassName(styles.header, styles.starsHeader)}>
|
||||||
{media && (
|
{media && (
|
||||||
<PaidMediaThumb
|
<PaidMediaThumb
|
||||||
className={buildClassName(styles.mediaPreview, 'transaction-media-preview')}
|
className={buildClassName(styles.mediaPreview, 'transaction-media-preview')}
|
||||||
@ -92,45 +158,57 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
onClick={handleOpenMedia}
|
onClick={handleOpenMedia}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<img
|
{isGift ? animatedStickerData : (
|
||||||
className={buildClassName(styles.starsBackground, media && styles.mediaShift)}
|
<img
|
||||||
src={StarsBackground}
|
className={buildClassName(styles.starsBackground, media && styles.mediaShift)}
|
||||||
alt=""
|
src={StarsBackground}
|
||||||
draggable={false}
|
alt=""
|
||||||
/>
|
draggable={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{title && <h1 className={styles.title}>{title}</h1>}
|
{title && <h1 className={styles.title}>{title}</h1>}
|
||||||
|
{isGift && (
|
||||||
|
<h1 className={buildClassName(styles.title, styles.starTitle)}>
|
||||||
|
{transaction?.isMyGift ? oldLang('StarsGiftSent') : oldLang('StarsGiftReceived')}
|
||||||
|
</h1>
|
||||||
|
)}
|
||||||
<p className={styles.description}>{description}</p>
|
<p className={styles.description}>{description}</p>
|
||||||
<p className={styles.amount}>
|
<p className={styles.amount}>
|
||||||
<span className={buildClassName(styles.amount, transaction.stars < 0 ? styles.negative : styles.positive)}>
|
<span className={buildClassName(styles.amount, transaction.stars < 0 ? styles.negative : styles.positive)}>
|
||||||
{formatStarsTransactionAmount(transaction.stars)}
|
{formatStarsTransactionAmount(transaction.stars)}
|
||||||
</span>
|
</span>
|
||||||
<StarIcon type="gold" size="big" />
|
<StarIcon type="gold" size="middle" />
|
||||||
</p>
|
</p>
|
||||||
|
{isGift && (
|
||||||
|
<span className={styles.subtitle}>
|
||||||
|
{transaction?.isMyGift ? giftOutAboutText : giftEntryAboutText}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const tableData: TableData = [];
|
const tableData: TableData = [];
|
||||||
|
|
||||||
tableData.push([
|
tableData.push([
|
||||||
lang(transaction.stars < 0 || transaction.isMyGift ? 'Stars.Transaction.To'
|
oldLang(transaction.stars < 0 || transaction.isMyGift ? 'Stars.Transaction.To'
|
||||||
: peerId ? 'Star.Transaction.From' : 'Stars.Transaction.Via'),
|
: peerId ? 'Star.Transaction.From' : 'Stars.Transaction.Via'),
|
||||||
peerId ? { chatId: peerId } : toName || '',
|
peerId ? { chatId: peerId } : toName || '',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (messageLink) {
|
if (messageLink) {
|
||||||
tableData.push([lang('Stars.Transaction.Media'), <SafeLink url={messageLink} text={messageLink} />]);
|
tableData.push([oldLang('Stars.Transaction.Media'), <SafeLink url={messageLink} text={messageLink} />]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transaction.id) {
|
if (transaction.id) {
|
||||||
tableData.push([
|
tableData.push([
|
||||||
lang('Stars.Transaction.Id'),
|
oldLang('Stars.Transaction.Id'),
|
||||||
(
|
(
|
||||||
<span
|
<span
|
||||||
className={styles.tid}
|
className={styles.tid}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
copyTextToClipboard(transaction.id!);
|
copyTextToClipboard(transaction.id!);
|
||||||
showNotification({
|
showNotification({
|
||||||
message: lang('StarsTransactionIDCopied'),
|
message: oldLang('StarsTransactionIDCopied'),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -142,17 +220,17 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
tableData.push([
|
tableData.push([
|
||||||
lang('Stars.Transaction.Date'),
|
oldLang('Stars.Transaction.Date'),
|
||||||
formatDateTimeToString(transaction.date * 1000, lang.code, true),
|
formatDateTimeToString(transaction.date * 1000, oldLang.code, true),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const footerText = lang('lng_credits_box_out_about');
|
const footerText = oldLang('lng_credits_box_out_about');
|
||||||
const footerTextParts = footerText.split('{link}');
|
const footerTextParts = footerText.split('{link}');
|
||||||
|
|
||||||
const footer = (
|
const footer = (
|
||||||
<span className={styles.footer}>
|
<span className={styles.footer}>
|
||||||
{footerTextParts[0]}
|
{footerTextParts[0]}
|
||||||
<SafeLink url={lang('StarsTOSLink')} text={lang('lng_credits_summary_options_about_link')} />
|
<SafeLink url={oldLang('StarsTOSLink')} text={oldLang('lng_credits_summary_options_about_link')} />
|
||||||
{footerTextParts[1]}
|
{footerTextParts[1]}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@ -163,7 +241,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
footer,
|
footer,
|
||||||
avatarPeer: !transaction.photo ? (peer || customPeer) : undefined,
|
avatarPeer: !transaction.photo ? (peer || customPeer) : undefined,
|
||||||
};
|
};
|
||||||
}, [lang, transaction, peer]);
|
}, [transaction, oldLang, peer, isGift, animatedStickerData, giftOutAboutText, giftEntryAboutText]);
|
||||||
|
|
||||||
const prevModalData = usePrevious(starModalData);
|
const prevModalData = usePrevious(starModalData);
|
||||||
const renderingModalData = prevModalData || starModalData;
|
const renderingModalData = prevModalData || starModalData;
|
||||||
@ -173,12 +251,13 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
isOpen={Boolean(transaction)}
|
isOpen={Boolean(transaction)}
|
||||||
className={styles.modal}
|
className={styles.modal}
|
||||||
header={renderingModalData?.header}
|
header={renderingModalData?.header}
|
||||||
|
isGift={isGift}
|
||||||
tableData={renderingModalData?.tableData}
|
tableData={renderingModalData?.tableData}
|
||||||
footer={renderingModalData?.footer}
|
footer={renderingModalData?.footer}
|
||||||
noHeaderImage={Boolean(transaction?.extendedMedia)}
|
noHeaderImage={Boolean(transaction?.extendedMedia)}
|
||||||
headerAvatarWebPhoto={transaction?.photo}
|
headerAvatarWebPhoto={transaction?.photo}
|
||||||
headerAvatarPeer={renderingModalData?.avatarPeer}
|
headerAvatarPeer={renderingModalData?.avatarPeer}
|
||||||
buttonText={lang('OK')}
|
buttonText={oldLang('OK')}
|
||||||
onClose={closeStarsTransactionModal}
|
onClose={closeStarsTransactionModal}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -188,9 +267,16 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
(global, { modal }): StateProps => {
|
(global, { modal }): StateProps => {
|
||||||
const peerId = modal?.transaction?.peer?.type === 'peer' && modal.transaction.peer.id;
|
const peerId = modal?.transaction?.peer?.type === 'peer' && modal.transaction.peer.id;
|
||||||
const peer = peerId ? selectPeer(global, peerId) : undefined;
|
const peer = peerId ? selectPeer(global, peerId) : undefined;
|
||||||
|
const user = peerId ? selectUser(global, peerId) : undefined;
|
||||||
|
|
||||||
|
const starCount = modal?.transaction.stars;
|
||||||
|
const starGiftSticker = selectGiftStickerForStars(global, starCount);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
peer,
|
peer,
|
||||||
|
user,
|
||||||
|
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
|
||||||
|
starGiftSticker,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(StarsTransactionModal));
|
)(StarsTransactionModal));
|
||||||
|
|||||||
@ -583,7 +583,8 @@ addActionHandler('closePremiumGiftModal', (global, actions, payload): ActionRetu
|
|||||||
|
|
||||||
addActionHandler('openStarsGiftModal', async (global, actions, payload): Promise<void> => {
|
addActionHandler('openStarsGiftModal', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
forUserId, tabId = getCurrentTabId(),
|
forUserId,
|
||||||
|
tabId = getCurrentTabId(),
|
||||||
} = payload || {};
|
} = payload || {};
|
||||||
|
|
||||||
const starsGiftOptions = await callApi('getStarsGiftOptions', {});
|
const starsGiftOptions = await callApi('getStarsGiftOptions', {});
|
||||||
|
|||||||
@ -64,6 +64,23 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inputInvoice?.type === 'stars') {
|
||||||
|
if (!inputInvoice.stars) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const starsModalState = selectTabState(global, tabId).starsGiftModal;
|
||||||
|
|
||||||
|
if (starsModalState && starsModalState.isOpen) {
|
||||||
|
global = updateTabState(global, {
|
||||||
|
starsGiftModal: {
|
||||||
|
...starsModalState,
|
||||||
|
isCompleted: true,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
global = closeInvoice(global, tabId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,12 @@ const MONTH_EMOTICON: Record<number, string> = {
|
|||||||
24: `${5}\u{FE0F}\u20E3`,
|
24: `${5}\u{FE0F}\u20E3`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const STAR_EMOTICON: Record<number, string> = {
|
||||||
|
1000: `${2}\u{FE0F}\u20E3`,
|
||||||
|
2500: `${3}\u{FE0F}\u20E3`,
|
||||||
|
5000: `${4}\u{FE0F}\u20E3`,
|
||||||
|
};
|
||||||
|
|
||||||
export function selectIsStickerFavorite<T extends GlobalState>(global: T, sticker: ApiSticker) {
|
export function selectIsStickerFavorite<T extends GlobalState>(global: T, sticker: ApiSticker) {
|
||||||
const { stickers } = global.stickers.favorite;
|
const { stickers } = global.stickers.favorite;
|
||||||
return stickers && stickers.some(({ id }) => id === sticker.id);
|
return stickers && stickers.some(({ id }) => id === sticker.id);
|
||||||
@ -156,3 +162,20 @@ export function selectGiftStickerForDuration<T extends GlobalState>(global: T, d
|
|||||||
const emoji = MONTH_EMOTICON[duration];
|
const emoji = MONTH_EMOTICON[duration];
|
||||||
return stickers.find((sticker) => sticker.emoji === emoji) || stickers[0];
|
return stickers.find((sticker) => sticker.emoji === emoji) || stickers[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectGiftStickerForStars<T extends GlobalState>(global: T, starCount?: number) {
|
||||||
|
const stickers = global.premiumGifts?.stickers;
|
||||||
|
|
||||||
|
if (!stickers || !starCount) return undefined;
|
||||||
|
|
||||||
|
let emoji;
|
||||||
|
if (starCount <= 1000) {
|
||||||
|
emoji = STAR_EMOTICON[1000];
|
||||||
|
} else if (starCount < 2500) {
|
||||||
|
emoji = STAR_EMOTICON[2500];
|
||||||
|
} else {
|
||||||
|
emoji = STAR_EMOTICON[5000];
|
||||||
|
}
|
||||||
|
|
||||||
|
return stickers.find((sticker) => sticker.emoji === emoji) || stickers[0];
|
||||||
|
}
|
||||||
|
|||||||
@ -63,7 +63,7 @@ import type {
|
|||||||
ApiSendMessageAction,
|
ApiSendMessageAction,
|
||||||
ApiSession,
|
ApiSession,
|
||||||
ApiSessionData,
|
ApiSessionData,
|
||||||
ApiSponsoredMessage, ApiStarsGiftOption,
|
ApiSponsoredMessage,
|
||||||
ApiStarsTransaction,
|
ApiStarsTransaction,
|
||||||
ApiStarTopupOption,
|
ApiStarTopupOption,
|
||||||
ApiStealthMode,
|
ApiStealthMode,
|
||||||
@ -725,7 +725,7 @@ export type TabState = {
|
|||||||
isCompleted?: boolean;
|
isCompleted?: boolean;
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
forUserId?: string;
|
forUserId?: string;
|
||||||
starsGiftOptions?: ApiStarsGiftOption[];
|
starsGiftOptions?: ApiStarTopupOption[];
|
||||||
};
|
};
|
||||||
|
|
||||||
starsTransactionModal?: {
|
starsTransactionModal?: {
|
||||||
@ -737,7 +737,7 @@ export type TabState = {
|
|||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
forUserIds?: string[];
|
forUserIds?: string[];
|
||||||
gifts?: ApiPremiumGiftCodeOption[];
|
gifts?: ApiPremiumGiftCodeOption[];
|
||||||
starsGiftOptions?: ApiStarsGiftOption[];
|
starsGiftOptions?: ApiStarTopupOption[];
|
||||||
};
|
};
|
||||||
|
|
||||||
limitReachedModal?: {
|
limitReachedModal?: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user