Management / Boosts: Add giveaway and gift tabs, various fixes (#4453)
Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
This commit is contained in:
parent
3a1c87fcb0
commit
8b31f33685
@ -2,6 +2,7 @@ import { Api as GramJs } from '../../../lib/gramjs';
|
|||||||
|
|
||||||
import type { ApiPremiumSection } from '../../../global/types';
|
import type { ApiPremiumSection } from '../../../global/types';
|
||||||
import type {
|
import type {
|
||||||
|
ApiBoost,
|
||||||
ApiBoostsStatus,
|
ApiBoostsStatus,
|
||||||
ApiCheckedGiftCode,
|
ApiCheckedGiftCode,
|
||||||
ApiGiveawayInfo,
|
ApiGiveawayInfo,
|
||||||
@ -223,6 +224,24 @@ export function buildApiBoostsStatus(boostStatus: GramJs.premium.BoostsStatus):
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildApiBoost(boost: GramJs.Boost): ApiBoost {
|
||||||
|
const {
|
||||||
|
userId,
|
||||||
|
multiplier,
|
||||||
|
expires,
|
||||||
|
giveaway,
|
||||||
|
gift,
|
||||||
|
} = boost;
|
||||||
|
|
||||||
|
return {
|
||||||
|
userId: userId && buildApiPeerId(userId, 'user'),
|
||||||
|
multiplier,
|
||||||
|
expires,
|
||||||
|
isFromGiveaway: giveaway,
|
||||||
|
isGift: gift,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function buildApiMyBoost(myBoost: GramJs.MyBoost): ApiMyBoost {
|
export function buildApiMyBoost(myBoost: GramJs.MyBoost): ApiMyBoost {
|
||||||
const {
|
const {
|
||||||
date, expires, slot, cooldownUntilDate, peer,
|
date, expires, slot, cooldownUntilDate, peer,
|
||||||
|
|||||||
@ -6,9 +6,9 @@ import type {
|
|||||||
OnApiUpdate,
|
OnApiUpdate,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { buildCollectionByCallback } from '../../../util/iteratees';
|
|
||||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||||
import {
|
import {
|
||||||
|
buildApiBoost,
|
||||||
buildApiBoostsStatus,
|
buildApiBoostsStatus,
|
||||||
buildApiCheckedGiftCode,
|
buildApiCheckedGiftCode,
|
||||||
buildApiGiveawayInfo,
|
buildApiGiveawayInfo,
|
||||||
@ -266,15 +266,18 @@ export async function fetchBoostStatus({
|
|||||||
|
|
||||||
export async function fetchBoostList({
|
export async function fetchBoostList({
|
||||||
chat,
|
chat,
|
||||||
|
isGifts,
|
||||||
offset = '',
|
offset = '',
|
||||||
limit,
|
limit,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
|
isGifts?: boolean;
|
||||||
offset?: string;
|
offset?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
}) {
|
}) {
|
||||||
const result = await invokeRequest(new GramJs.premium.GetBoostsList({
|
const result = await invokeRequest(new GramJs.premium.GetBoostsList({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
|
gifts: isGifts || undefined,
|
||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
}));
|
}));
|
||||||
@ -287,17 +290,12 @@ export async function fetchBoostList({
|
|||||||
|
|
||||||
const users = result.users.map(buildApiUser).filter(Boolean);
|
const users = result.users.map(buildApiUser).filter(Boolean);
|
||||||
|
|
||||||
const userBoosts = result.boosts.filter((boost) => boost.userId);
|
const boostList = result.boosts.map(buildApiBoost);
|
||||||
const boosterIds = userBoosts.map((boost) => boost.userId!.toString());
|
|
||||||
const boosters = buildCollectionByCallback(userBoosts, (boost) => (
|
|
||||||
[boost.userId!.toString(), boost.expires]
|
|
||||||
));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
count: result.count,
|
count: result.count,
|
||||||
|
boostList,
|
||||||
users,
|
users,
|
||||||
boosters,
|
|
||||||
boosterIds,
|
|
||||||
nextOffset: result.nextOffset,
|
nextOffset: result.nextOffset,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -132,6 +132,14 @@ export type ApiMyBoost = {
|
|||||||
cooldownUntil?: number;
|
cooldownUntil?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiBoost = {
|
||||||
|
userId?: string;
|
||||||
|
multiplier?: number;
|
||||||
|
expires: number;
|
||||||
|
isFromGiveaway?: boolean;
|
||||||
|
isGift?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiGiveawayInfoActive = {
|
export type ApiGiveawayInfoActive = {
|
||||||
type: 'active';
|
type: 'active';
|
||||||
isParticipating?: true;
|
isParticipating?: true;
|
||||||
|
|||||||
@ -257,4 +257,8 @@
|
|||||||
&.hidden-user {
|
&.hidden-user {
|
||||||
--color-user: var(--color-deleted-account);
|
--color-user: var(--color-deleted-account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.unknown-user {
|
||||||
|
background: var(--premium-gradient);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@ type OwnProps = {
|
|||||||
photo?: ApiPhoto;
|
photo?: ApiPhoto;
|
||||||
text?: string;
|
text?: string;
|
||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
|
isUnknownUser?: boolean;
|
||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
withVideo?: boolean;
|
withVideo?: boolean;
|
||||||
withStory?: boolean;
|
withStory?: boolean;
|
||||||
@ -77,6 +78,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
text,
|
text,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
|
isUnknownUser,
|
||||||
withVideo,
|
withVideo,
|
||||||
withStory,
|
withStory,
|
||||||
forPremiumPromo,
|
forPremiumPromo,
|
||||||
@ -120,6 +122,10 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const specialIcon = useMemo(() => {
|
const specialIcon = useMemo(() => {
|
||||||
|
if (isUnknownUser) {
|
||||||
|
return 'user';
|
||||||
|
}
|
||||||
|
|
||||||
if (isSavedMessages) {
|
if (isSavedMessages) {
|
||||||
return isSavedDialog ? 'my-notes' : 'avatar-saved-messages';
|
return isSavedDialog ? 'my-notes' : 'avatar-saved-messages';
|
||||||
}
|
}
|
||||||
@ -137,7 +143,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [isAnonymousForwards, isDeleted, isSavedDialog, isReplies, isSavedMessages]);
|
}, [isUnknownUser, isSavedMessages, isDeleted, isReplies, isAnonymousForwards, isSavedDialog]);
|
||||||
|
|
||||||
const imgBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl);
|
const imgBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl);
|
||||||
const videoBlobUrl = useMedia(videoHash, !shouldLoadVideo, ApiMediaFormat.BlobUrl);
|
const videoBlobUrl = useMedia(videoHash, !shouldLoadVideo, ApiMediaFormat.BlobUrl);
|
||||||
@ -215,6 +221,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
`Avatar size-${size}`,
|
`Avatar size-${size}`,
|
||||||
className,
|
className,
|
||||||
getPeerColorClass(peer),
|
getPeerColorClass(peer),
|
||||||
|
isUnknownUser && 'unknown-user',
|
||||||
!peer && text && 'hidden-user',
|
!peer && text && 'hidden-user',
|
||||||
isSavedMessages && 'saved-messages',
|
isSavedMessages && 'saved-messages',
|
||||||
isAnonymousForwards && 'anonymous-forwards',
|
isAnonymousForwards && 'anonymous-forwards',
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import VerifiedIcon from './VerifiedIcon';
|
|||||||
import styles from './FullNameTitle.module.scss';
|
import styles from './FullNameTitle.module.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
peer: ApiPeer;
|
peer?: ApiPeer;
|
||||||
className?: string;
|
className?: string;
|
||||||
noVerified?: boolean;
|
noVerified?: boolean;
|
||||||
noFake?: boolean;
|
noFake?: boolean;
|
||||||
@ -34,9 +34,11 @@ type OwnProps = {
|
|||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
noLoopLimit?: boolean;
|
noLoopLimit?: boolean;
|
||||||
|
isUnknownUser?: boolean;
|
||||||
canCopyTitle?: boolean;
|
canCopyTitle?: boolean;
|
||||||
onEmojiStatusClick?: NoneToVoidFunction;
|
onEmojiStatusClick?: NoneToVoidFunction;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
|
iconElement?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FullNameTitle: FC<OwnProps> = ({
|
const FullNameTitle: FC<OwnProps> = ({
|
||||||
@ -52,13 +54,26 @@ const FullNameTitle: FC<OwnProps> = ({
|
|||||||
canCopyTitle,
|
canCopyTitle,
|
||||||
onEmojiStatusClick,
|
onEmojiStatusClick,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
|
iconElement,
|
||||||
|
isUnknownUser,
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const { showNotification } = getActions();
|
const { showNotification } = getActions();
|
||||||
const isUser = isUserId(peer.id);
|
const isUser = peer && isUserId(peer.id);
|
||||||
const title = isUser ? getUserFullName(peer as ApiUser) : getChatTitle(lang, peer as ApiChat);
|
|
||||||
const isPremium = isUser && (peer as ApiUser).isPremium;
|
const isPremium = isUser && (peer as ApiUser).isPremium;
|
||||||
|
|
||||||
|
const title = useMemo(() => {
|
||||||
|
if (isUnknownUser) {
|
||||||
|
return lang('BoostingToBeDistributed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peer && isUserId(peer.id)) {
|
||||||
|
return getUserFullName(peer as ApiUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer && getChatTitle(lang, peer as ApiChat);
|
||||||
|
}, [isUnknownUser, lang, peer]);
|
||||||
|
|
||||||
const handleTitleClick = useLastCallback((e) => {
|
const handleTitleClick = useLastCallback((e) => {
|
||||||
if (!title || !canCopyTitle) {
|
if (!title || !canCopyTitle) {
|
||||||
return;
|
return;
|
||||||
@ -74,16 +89,16 @@ const FullNameTitle: FC<OwnProps> = ({
|
|||||||
return lang(isSavedDialog ? 'MyNotes' : 'SavedMessages');
|
return lang(isSavedDialog ? 'MyNotes' : 'SavedMessages');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAnonymousForwardsChat(peer.id)) {
|
if (peer && isAnonymousForwardsChat(peer.id)) {
|
||||||
return lang('AnonymousForward');
|
return lang('AnonymousForward');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isChatWithRepliesBot(peer.id)) {
|
if (peer && isChatWithRepliesBot(peer.id)) {
|
||||||
return lang('RepliesTitle');
|
return lang('RepliesTitle');
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [isSavedDialog, isSavedMessages, lang, peer.id]);
|
}, [isSavedDialog, isSavedMessages, lang, peer]);
|
||||||
|
|
||||||
if (specialTitle) {
|
if (specialTitle) {
|
||||||
return (
|
return (
|
||||||
@ -103,18 +118,23 @@ const FullNameTitle: FC<OwnProps> = ({
|
|||||||
>
|
>
|
||||||
{renderText(title || '')}
|
{renderText(title || '')}
|
||||||
</h3>
|
</h3>
|
||||||
{!noVerified && peer.isVerified && <VerifiedIcon />}
|
{!iconElement && peer && (
|
||||||
{!noFake && peer.fakeType && <FakeIcon fakeType={peer.fakeType} />}
|
<>
|
||||||
{withEmojiStatus && peer.emojiStatus && (
|
{!noVerified && peer?.isVerified && <VerifiedIcon />}
|
||||||
<CustomEmoji
|
{!noFake && peer?.fakeType && <FakeIcon fakeType={peer.fakeType} />}
|
||||||
documentId={peer.emojiStatus.documentId}
|
{withEmojiStatus && peer.emojiStatus && (
|
||||||
size={emojiStatusSize}
|
<CustomEmoji
|
||||||
loopLimit={!noLoopLimit ? EMOJI_STATUS_LOOP_LIMIT : undefined}
|
documentId={peer.emojiStatus.documentId}
|
||||||
observeIntersectionForLoading={observeIntersection}
|
size={emojiStatusSize}
|
||||||
onClick={onEmojiStatusClick}
|
loopLimit={!noLoopLimit ? EMOJI_STATUS_LOOP_LIMIT : undefined}
|
||||||
/>
|
observeIntersectionForLoading={observeIntersection}
|
||||||
|
onClick={onEmojiStatusClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{withEmojiStatus && !peer.emojiStatus && isPremium && <PremiumIcon />}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{withEmojiStatus && !peer.emojiStatus && isPremium && <PremiumIcon />}
|
{iconElement}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,9 +3,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
background: #F1F3F5;
|
background: var(--color-background-menu-separator);
|
||||||
border-radius: 0.625rem;
|
border-radius: 0.625rem;
|
||||||
color: black;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.withBadge {
|
.withBadge {
|
||||||
|
|||||||
@ -9,7 +9,9 @@ import type { StoryViewerOrigin } from '../../types';
|
|||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
import { MediaViewerOrigin } from '../../types';
|
import { MediaViewerOrigin } from '../../types';
|
||||||
|
|
||||||
import { getMainUsername, getUserStatus, isUserOnline } from '../../global/helpers';
|
import {
|
||||||
|
getMainUsername, getUserStatus, isUserOnline,
|
||||||
|
} from '../../global/helpers';
|
||||||
import { selectChatMessages, selectUser, selectUserStatus } from '../../global/selectors';
|
import { selectChatMessages, selectUser, selectUserStatus } from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
@ -36,10 +38,13 @@ type OwnProps = {
|
|||||||
withMediaViewer?: boolean;
|
withMediaViewer?: boolean;
|
||||||
withUsername?: boolean;
|
withUsername?: boolean;
|
||||||
withStory?: boolean;
|
withStory?: boolean;
|
||||||
|
isUnknownUser?: boolean;
|
||||||
withFullInfo?: boolean;
|
withFullInfo?: boolean;
|
||||||
withUpdatingStatus?: boolean;
|
withUpdatingStatus?: boolean;
|
||||||
storyViewerOrigin?: StoryViewerOrigin;
|
storyViewerOrigin?: StoryViewerOrigin;
|
||||||
noEmojiStatus?: boolean;
|
noEmojiStatus?: boolean;
|
||||||
|
noFake?: boolean;
|
||||||
|
noVerified?: boolean;
|
||||||
emojiStatusSize?: number;
|
emojiStatusSize?: number;
|
||||||
noStatusOrTyping?: boolean;
|
noStatusOrTyping?: boolean;
|
||||||
noRtl?: boolean;
|
noRtl?: boolean;
|
||||||
@ -47,6 +52,8 @@ type OwnProps = {
|
|||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
onEmojiStatusClick?: NoneToVoidFunction;
|
onEmojiStatusClick?: NoneToVoidFunction;
|
||||||
|
iconElement?: React.ReactNode;
|
||||||
|
rightElement?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps =
|
type StateProps =
|
||||||
@ -73,6 +80,9 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
emojiStatusSize,
|
emojiStatusSize,
|
||||||
noStatusOrTyping,
|
noStatusOrTyping,
|
||||||
noEmojiStatus,
|
noEmojiStatus,
|
||||||
|
noFake,
|
||||||
|
noVerified,
|
||||||
|
isUnknownUser,
|
||||||
noRtl,
|
noRtl,
|
||||||
user,
|
user,
|
||||||
userStatus,
|
userStatus,
|
||||||
@ -86,6 +96,8 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
storyViewerOrigin,
|
storyViewerOrigin,
|
||||||
isSynced,
|
isSynced,
|
||||||
onEmojiStatusClick,
|
onEmojiStatusClick,
|
||||||
|
iconElement,
|
||||||
|
rightElement,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadFullUser,
|
loadFullUser,
|
||||||
@ -119,7 +131,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const mainUsername = useMemo(() => user && withUsername && getMainUsername(user), [user, withUsername]);
|
const mainUsername = useMemo(() => user && withUsername && getMainUsername(user), [user, withUsername]);
|
||||||
|
|
||||||
if (!user) {
|
if (!user && !isUnknownUser) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,11 +195,15 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<FullNameTitle
|
<FullNameTitle
|
||||||
peer={user!}
|
peer={user!}
|
||||||
|
noFake={noFake}
|
||||||
|
noVerified={noVerified}
|
||||||
withEmojiStatus={!noEmojiStatus}
|
withEmojiStatus={!noEmojiStatus}
|
||||||
emojiStatusSize={emojiStatusSize}
|
emojiStatusSize={emojiStatusSize}
|
||||||
isSavedMessages={isSavedMessages}
|
isSavedMessages={isSavedMessages}
|
||||||
isSavedDialog={isSavedDialog}
|
isSavedDialog={isSavedDialog}
|
||||||
onEmojiStatusClick={onEmojiStatusClick}
|
onEmojiStatusClick={onEmojiStatusClick}
|
||||||
|
isUnknownUser={isUnknownUser}
|
||||||
|
iconElement={iconElement}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -204,11 +220,12 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Avatar
|
<Avatar
|
||||||
key={user.id}
|
key={user?.id}
|
||||||
size={avatarSize}
|
size={avatarSize}
|
||||||
peer={user}
|
peer={user}
|
||||||
className={buildClassName(isSavedDialog && 'overlay-avatar')}
|
className={buildClassName(isSavedDialog && 'overlay-avatar')}
|
||||||
isSavedMessages={isSavedMessages}
|
isSavedMessages={isSavedMessages}
|
||||||
|
isUnknownUser={isUnknownUser}
|
||||||
isSavedDialog={isSavedDialog}
|
isSavedDialog={isSavedDialog}
|
||||||
withStory={withStory}
|
withStory={withStory}
|
||||||
storyViewerOrigin={storyViewerOrigin}
|
storyViewerOrigin={storyViewerOrigin}
|
||||||
@ -220,6 +237,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
{(status || (!isSavedMessages && !noStatusOrTyping)) && renderStatusOrTyping()}
|
{(status || (!isSavedMessages && !noStatusOrTyping)) && renderStatusOrTyping()}
|
||||||
</div>
|
</div>
|
||||||
{ripple && <RippleEffect />}
|
{ripple && <RippleEffect />}
|
||||||
|
{rightElement}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import mastercardIconPath from '../../assets/mastercard.svg';
|
|||||||
import mirIconPath from '../../assets/mir.svg';
|
import mirIconPath from '../../assets/mir.svg';
|
||||||
import visaIconPath from '../../assets/visa.svg';
|
import visaIconPath from '../../assets/visa.svg';
|
||||||
|
|
||||||
const CARD_NUMBER_MAX_LENGTH = 23;
|
const CARD_NUMBER_MAX_LENGTH = 19;
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
value: string;
|
value: string;
|
||||||
|
|||||||
@ -529,7 +529,7 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
|||||||
|
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case PaymentStep.Checkout:
|
case PaymentStep.Checkout:
|
||||||
return Boolean(invoice?.isRecurring && !isTosAccepted);
|
return !isTosAccepted;
|
||||||
|
|
||||||
case PaymentStep.PaymentInfo:
|
case PaymentStep.PaymentInfo:
|
||||||
return Boolean(
|
return Boolean(
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 0.9375rem;
|
font-size: 0.9375rem;
|
||||||
|
padding: 0.75rem 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header {
|
.section-header {
|
||||||
@ -17,7 +18,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
padding: 1rem;
|
padding: 0.625rem;
|
||||||
@include mixins.side-panel-section;
|
@include mixins.side-panel-section;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,18 +73,36 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.floatingBadgeButtonColor {
|
.floatingBadgeButtonColor {
|
||||||
padding: 0.25rem 0.75rem 0.375rem 0.5625rem;
|
padding: 0 0.5rem 0 0.375rem;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
background-color: var(--color-primary-opacity);
|
background-color: var(--color-primary-opacity);
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.floatingBadgeWarning {
|
||||||
|
color: var(--color-orange);
|
||||||
|
background: var( --color-light-coral);
|
||||||
|
}
|
||||||
|
|
||||||
|
.floatingBadgeButton {
|
||||||
|
padding: 0.125rem 0.75rem 0.125rem 0.5625rem;
|
||||||
|
}
|
||||||
|
|
||||||
.floatingBadgeIcon {
|
.floatingBadgeIcon {
|
||||||
font-size: 1.125rem;
|
font-size: 0.875rem;
|
||||||
margin-right: 0.1875rem;
|
margin-right: 0.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.floatingBadgeValue {
|
.floatingBadgeValue {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boostSection {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,12 +1,20 @@
|
|||||||
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
import React, {
|
||||||
|
memo, useMemo, useRef, useState,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiBoostStatistics, ApiPrepaidGiveaway } from '../../../api/types';
|
import type { ApiBoostStatistics, ApiPrepaidGiveaway } from '../../../api/types';
|
||||||
import type { TabState } from '../../../global/types';
|
import type { TabState } from '../../../global/types';
|
||||||
|
|
||||||
import { GIVEAWAY_BOOST_PER_PREMIUM } from '../../../config';
|
import {
|
||||||
|
GIVEAWAY_BOOST_PER_PREMIUM,
|
||||||
|
} from '../../../config';
|
||||||
import { isChatChannel } from '../../../global/helpers';
|
import { isChatChannel } from '../../../global/helpers';
|
||||||
import { selectChat, selectIsGiveawayGiftsPurchaseAvailable, selectTabState } from '../../../global/selectors';
|
import {
|
||||||
|
selectChat,
|
||||||
|
selectIsGiveawayGiftsPurchaseAvailable,
|
||||||
|
selectTabState,
|
||||||
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { formatDateAtTime } from '../../../util/date/dateFormat';
|
import { formatDateAtTime } from '../../../util/date/dateFormat';
|
||||||
import { getBoostProgressInfo } from '../../common/helpers/boostInfo';
|
import { getBoostProgressInfo } from '../../common/helpers/boostInfo';
|
||||||
@ -21,6 +29,8 @@ import PrivateChatInfo from '../../common/PrivateChatInfo';
|
|||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
import Loading from '../../ui/Loading';
|
import Loading from '../../ui/Loading';
|
||||||
import Spinner from '../../ui/Spinner';
|
import Spinner from '../../ui/Spinner';
|
||||||
|
import TabList from '../../ui/TabList';
|
||||||
|
import Transition from '../../ui/Transition';
|
||||||
import StatisticsOverview from './StatisticsOverview';
|
import StatisticsOverview from './StatisticsOverview';
|
||||||
|
|
||||||
import styles from './BoostStatistics.module.scss';
|
import styles from './BoostStatistics.module.scss';
|
||||||
@ -51,13 +61,20 @@ const BoostStatistics = ({
|
|||||||
isChannel,
|
isChannel,
|
||||||
}: StateProps) => {
|
}: StateProps) => {
|
||||||
const {
|
const {
|
||||||
openChat, loadMoreBoosters, closeBoostStatistics, openGiveawayModal,
|
openChat, loadMoreBoosters, closeBoostStatistics, openGiveawayModal, showNotification,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const transitionRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const isLoaded = boostStatistics?.boostStatus;
|
const isLoaded = boostStatistics?.boostStatus;
|
||||||
const status = isLoaded ? boostStatistics.boostStatus : undefined;
|
const status = isLoaded ? boostStatistics.boostStatus : undefined;
|
||||||
|
|
||||||
|
const isGiftListEqual = boostStatistics && boostStatistics?.boosts?.count
|
||||||
|
=== boostStatistics?.giftedBoosts?.count;
|
||||||
|
const shouldDisplayGiftList = !isGiftListEqual && boostStatistics?.giftedBoosts
|
||||||
|
&& boostStatistics?.giftedBoosts?.list?.length > 0;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
currentLevel,
|
currentLevel,
|
||||||
hasNextLevel,
|
hasNextLevel,
|
||||||
@ -90,30 +107,140 @@ const BoostStatistics = ({
|
|||||||
} satisfies ApiBoostStatistics;
|
} satisfies ApiBoostStatistics;
|
||||||
}, [status, boosts, currentLevel, remainingBoosts]);
|
}, [status, boosts, currentLevel, remainingBoosts]);
|
||||||
|
|
||||||
const boostersToLoadCount = useMemo(() => {
|
const tabs = useMemo(() => {
|
||||||
if (!boostStatistics?.count) return undefined;
|
if (shouldDisplayGiftList) {
|
||||||
const loadedCount = boostStatistics.boosterIds?.length || 0;
|
return [
|
||||||
const totalCount = boostStatistics.count;
|
{ type: 'boostList', title: lang('BoostingBoostsCount', boostStatistics?.boosts?.count) },
|
||||||
return totalCount - loadedCount;
|
{ type: 'giftedBoostList', title: lang('BoostingGiftsCount', boostStatistics?.giftedBoosts?.count) },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, [shouldDisplayGiftList, boostStatistics?.boosts?.count, boostStatistics?.giftedBoosts?.count, lang]);
|
||||||
|
|
||||||
|
const initialTab = useMemo(() => {
|
||||||
|
return boostStatistics?.boosts && boostStatistics.boosts?.list.length > 0 ? 1 : 0;
|
||||||
}, [boostStatistics]);
|
}, [boostStatistics]);
|
||||||
|
|
||||||
const handleBoosterClick = useLastCallback((userId: string) => {
|
const [activeTab, setActiveTab] = useState(initialTab);
|
||||||
|
|
||||||
|
const renderingActiveTab = activeTab > tabs.length - 1 ? tabs.length - 1 : activeTab;
|
||||||
|
|
||||||
|
const tabType = tabs[renderingActiveTab]?.type;
|
||||||
|
|
||||||
|
const activeKey = tabs.findIndex(({ type }) => type === tabType);
|
||||||
|
|
||||||
|
const boostersToLoadCount = useMemo(() => {
|
||||||
|
if (!boostStatistics) return undefined;
|
||||||
|
|
||||||
|
const list = shouldDisplayGiftList ? (tabType === 'boostList'
|
||||||
|
? boostStatistics.boosts : boostStatistics.giftedBoosts) : boostStatistics.boosts;
|
||||||
|
if (!list?.count) return undefined;
|
||||||
|
|
||||||
|
const loadedBoostsCount = list.list.reduce((total, boost) => {
|
||||||
|
return total + (boost.multiplier || 1);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const totalCount = list.count;
|
||||||
|
const toLoadCount = totalCount - loadedBoostsCount;
|
||||||
|
|
||||||
|
return toLoadCount > 0 ? toLoadCount : undefined;
|
||||||
|
}, [shouldDisplayGiftList, boostStatistics, tabType]);
|
||||||
|
|
||||||
|
const renderBoostIcon = useLastCallback((multiplier: number) => (
|
||||||
|
<div className={styles.quantity}>
|
||||||
|
<div className={buildClassName(styles.floatingBadge, styles.floatingBadgeButtonColor)}>
|
||||||
|
<Icon name="boost" className={styles.floatingBadgeIcon} />
|
||||||
|
<div className={styles.floatingBadgeValue}>{multiplier}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
const renderBoostTypeIcon = useLastCallback((boost) => {
|
||||||
|
if (!boost.isFromGiveaway && !boost.isGift) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.quantity}>
|
||||||
|
<div className={buildClassName(styles.floatingBadge,
|
||||||
|
!boost.giveaway && styles.floatingBadgeWarning,
|
||||||
|
styles.floatingBadgeButtonColor,
|
||||||
|
styles.floatingBadgeButton)}
|
||||||
|
>
|
||||||
|
<Icon name="gift" className={styles.floatingBadgeIcon} />
|
||||||
|
<div className={styles.floatingBadgeValue}>{lang(boost.giveaway
|
||||||
|
? 'lng_prizes_results_link' : 'BoostingGift')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleBoosterClick = useLastCallback((userId?: string) => {
|
||||||
|
if (!userId) {
|
||||||
|
showNotification({
|
||||||
|
message: lang('BoostingRecipientWillBeSelected'),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
openChat({ id: userId });
|
openChat({ id: userId });
|
||||||
closeBoostStatistics();
|
closeBoostStatistics();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const renderBoostList = useLastCallback((boost) => {
|
||||||
|
return (
|
||||||
|
<ListItem
|
||||||
|
className="chat-item-clickable"
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => handleBoosterClick(boost.userId)}
|
||||||
|
>
|
||||||
|
<PrivateChatInfo
|
||||||
|
className={styles.user}
|
||||||
|
userId={boost.userId}
|
||||||
|
status={lang('BoostExpireOn', formatDateAtTime(lang, boost.expires * 1000))}
|
||||||
|
noEmojiStatus
|
||||||
|
forceShowSelf
|
||||||
|
noFake
|
||||||
|
noVerified
|
||||||
|
isUnknownUser={!boost.userId}
|
||||||
|
iconElement={boost.multiplier ? renderBoostIcon(boost.multiplier) : undefined}
|
||||||
|
rightElement={renderBoostTypeIcon(boost)}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const handleGiveawayClick = useLastCallback(() => {
|
const handleGiveawayClick = useLastCallback(() => {
|
||||||
openGiveawayModal({ chatId });
|
openGiveawayModal({ chatId });
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleLoadMore = useLastCallback(() => {
|
const handleLoadMore = useLastCallback(() => {
|
||||||
loadMoreBoosters();
|
loadMoreBoosters({ isGifts: tabType === 'giftedBoostList' });
|
||||||
});
|
});
|
||||||
|
|
||||||
const launchPrepaidGiveawayHandler = useLastCallback((prepaidGiveaway: ApiPrepaidGiveaway) => {
|
const launchPrepaidGiveawayHandler = useLastCallback((prepaidGiveaway: ApiPrepaidGiveaway) => {
|
||||||
openGiveawayModal({ chatId, prepaidGiveaway });
|
openGiveawayModal({ chatId, prepaidGiveaway });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function renderContent() {
|
||||||
|
let listToRender;
|
||||||
|
if (tabType === 'boostList') {
|
||||||
|
listToRender = boostStatistics?.boosts?.list;
|
||||||
|
} else if (tabType === 'giftedBoostList') {
|
||||||
|
listToRender = boostStatistics?.giftedBoosts?.list;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listToRender && !listToRender?.length) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.content}>
|
||||||
|
{listToRender?.map((boost) => renderBoostList(boost))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.root, 'custom-scroll')}>
|
<div className={buildClassName(styles.root, 'custom-scroll')}>
|
||||||
{!isLoaded && <Loading />}
|
{!isLoaded && <Loading />}
|
||||||
@ -152,7 +279,10 @@ const BoostStatistics = ({
|
|||||||
<p className={styles.month}>{lang('PrepaidGiveawayMonths', prepaidGiveaway.months)}</p>
|
<p className={styles.month}>{lang('PrepaidGiveawayMonths', prepaidGiveaway.months)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.quantity}>
|
<div className={styles.quantity}>
|
||||||
<div className={buildClassName(styles.floatingBadge, styles.floatingBadgeButtonColor)}>
|
<div className={buildClassName(styles.floatingBadge,
|
||||||
|
styles.floatingBadgeButtonColor,
|
||||||
|
styles.floatingBadgeButton)}
|
||||||
|
>
|
||||||
<Icon name="boost" className={styles.floatingBadgeIcon} />
|
<Icon name="boost" className={styles.floatingBadgeIcon} />
|
||||||
<div className={styles.floatingBadgeValue} dir={lang.isRtl ? 'rtl' : undefined}>
|
<div className={styles.floatingBadgeValue} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
{prepaidGiveaway.quantity * (giveawayBoostsPerPremium ?? GIVEAWAY_BOOST_PER_PREMIUM)}
|
{prepaidGiveaway.quantity * (giveawayBoostsPerPremium ?? GIVEAWAY_BOOST_PER_PREMIUM)}
|
||||||
@ -165,46 +295,52 @@ const BoostStatistics = ({
|
|||||||
<p className="text-muted hint" key="links-hint">{lang('BoostingSelectPaidGiveaway')}</p>
|
<p className="text-muted hint" key="links-hint">{lang('BoostingSelectPaidGiveaway')}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isChannel && (
|
<div className={styles.section}>
|
||||||
<div className={styles.section}>
|
{shouldDisplayGiftList ? (
|
||||||
<h4 className={styles.sectionHeader} dir={lang.isRtl ? 'rtl' : undefined}>
|
<div
|
||||||
{lang('Boosters')}
|
className={styles.boostSection}
|
||||||
</h4>
|
>
|
||||||
{!boostStatistics.boosterIds?.length && (
|
<Transition
|
||||||
<div className={styles.noResults}>{lang('NoBoostersHint')}</div>
|
key={activeKey}
|
||||||
)}
|
ref={transitionRef}
|
||||||
{boostStatistics.boosterIds?.map((userId) => (
|
name={lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized'}
|
||||||
<ListItem
|
activeKey={activeKey}
|
||||||
key={userId}
|
renderCount={tabs.length}
|
||||||
className="chat-item-clickable"
|
shouldRestoreHeight
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
className="shared-media-transition"
|
||||||
onClick={() => handleBoosterClick(userId)}
|
|
||||||
>
|
>
|
||||||
<PrivateChatInfo
|
{renderContent()}
|
||||||
className={styles.user}
|
</Transition>
|
||||||
forceShowSelf
|
<TabList big activeTab={renderingActiveTab} tabs={tabs} onSwitchTab={setActiveTab} />
|
||||||
userId={userId}
|
</div>
|
||||||
status={lang('BoostExpireOn', formatDateAtTime(lang, boostStatistics.boosters![userId] * 1000))}
|
) : (
|
||||||
/>
|
<>
|
||||||
</ListItem>
|
<h4 className={styles.sectionHeader} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
))}
|
{lang('BoostingBoostsCount', boostStatistics?.boosts?.count)}
|
||||||
{Boolean(boostersToLoadCount) && (
|
</h4>
|
||||||
<ListItem
|
{!boostStatistics?.boosts?.list?.length && (
|
||||||
key="load-more"
|
<div className={styles.noResults}>{lang(isChannel ? 'NoBoostersHint' : 'NoBoostersGroupHint')}
|
||||||
className={styles.showMore}
|
</div>
|
||||||
disabled={boostStatistics?.isLoadingBoosters}
|
)}
|
||||||
onClick={handleLoadMore}
|
{boostStatistics?.boosts?.list?.map((boost) => renderBoostList(boost))}
|
||||||
>
|
</>
|
||||||
{boostStatistics?.isLoadingBoosters ? (
|
)}
|
||||||
<Spinner className={styles.loadMoreSpinner} />
|
{Boolean(boostersToLoadCount) && (
|
||||||
) : (
|
<ListItem
|
||||||
<Icon name="down" className={styles.down} />
|
key="load-more"
|
||||||
)}
|
className={styles.showMore}
|
||||||
{lang('ShowVotes', boostersToLoadCount)}
|
disabled={boostStatistics?.isLoadingBoosters}
|
||||||
</ListItem>
|
onClick={handleLoadMore}
|
||||||
)}
|
>
|
||||||
</div>
|
{boostStatistics?.isLoadingBoosters ? (
|
||||||
)}
|
<Spinner className={styles.loadMoreSpinner} />
|
||||||
|
) : (
|
||||||
|
<Icon name="down" className={styles.down} />
|
||||||
|
)}
|
||||||
|
{lang('ShowVotes', boostersToLoadCount)}
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<LinkField className={styles.section} link={status!.boostUrl} withShare title={lang('LinkForBoosting')} />
|
<LinkField className={styles.section} link={status!.boostUrl} withShare title={lang('LinkForBoosting')} />
|
||||||
{isGiveawayAvailable && (
|
{isGiveawayAvailable && (
|
||||||
<div className={styles.section}>
|
<div className={styles.section}>
|
||||||
|
|||||||
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
&.big {
|
&.big {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
--border-radius-messages-small: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { PaymentStep } from '../../../types';
|
|||||||
|
|
||||||
import { DEBUG_PAYMENT_SMART_GLOCAL } from '../../../config';
|
import { DEBUG_PAYMENT_SMART_GLOCAL } from '../../../config';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import { buildCollectionByKey, unique } from '../../../util/iteratees';
|
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||||
import * as langProvider from '../../../util/langProvider';
|
import * as langProvider from '../../../util/langProvider';
|
||||||
import { getStripeError } from '../../../util/payments/stripe';
|
import { getStripeError } from '../../../util/payments/stripe';
|
||||||
import { buildQueryString } from '../../../util/requestQuery';
|
import { buildQueryString } from '../../../util/requestQuery';
|
||||||
@ -578,13 +578,15 @@ addActionHandler('openBoostStatistics', async (global, actions, payload): Promis
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
const [boostsListResult, boostStatusResult] = await Promise.all([
|
const [boostListResult, boostListGiftResult,
|
||||||
|
boostStatusResult] = await Promise.all([
|
||||||
callApi('fetchBoostList', { chat }),
|
callApi('fetchBoostList', { chat }),
|
||||||
|
callApi('fetchBoostList', { chat, isGifts: true }),
|
||||||
callApi('fetchBoostStatus', { chat }),
|
callApi('fetchBoostStatus', { chat }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
if (!boostsListResult || !boostStatusResult) {
|
if (!boostListResult || !boostListGiftResult || !boostStatusResult) {
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
boostStatistics: undefined,
|
boostStatistics: undefined,
|
||||||
}, tabId);
|
}, tabId);
|
||||||
@ -592,22 +594,28 @@ addActionHandler('openBoostStatistics', async (global, actions, payload): Promis
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
global = addUsers(global, buildCollectionByKey(boostsListResult.users, 'id'));
|
const totalBoostUserList = [...boostListResult.users, ...boostListGiftResult.users];
|
||||||
|
global = addUsers(global, buildCollectionByKey(totalBoostUserList, 'id'));
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
boostStatistics: {
|
boostStatistics: {
|
||||||
chatId,
|
chatId,
|
||||||
boostStatus: boostStatusResult,
|
boostStatus: boostStatusResult,
|
||||||
boosters: boostsListResult.boosters,
|
nextOffset: boostListResult.nextOffset,
|
||||||
boosterIds: boostsListResult.boosterIds,
|
boosts: {
|
||||||
count: boostsListResult.count,
|
count: boostListResult.count,
|
||||||
nextOffset: boostsListResult.nextOffset,
|
list: boostListResult.boostList,
|
||||||
|
},
|
||||||
|
giftedBoosts: {
|
||||||
|
count: boostListGiftResult?.count,
|
||||||
|
list: boostListGiftResult?.boostList,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('loadMoreBoosters', async (global, actions, payload): Promise<void> => {
|
addActionHandler('loadMoreBoosters', async (global, actions, payload): Promise<void> => {
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
const { isGifts, tabId = getCurrentTabId() } = payload || {};
|
||||||
let tabState = selectTabState(global, tabId);
|
let tabState = selectTabState(global, tabId);
|
||||||
if (!tabState.boostStatistics) return;
|
if (!tabState.boostStatistics) return;
|
||||||
|
|
||||||
@ -625,6 +633,7 @@ addActionHandler('loadMoreBoosters', async (global, actions, payload): Promise<v
|
|||||||
const result = await callApi('fetchBoostList', {
|
const result = await callApi('fetchBoostList', {
|
||||||
chat,
|
chat,
|
||||||
offset: tabState.boostStatistics.nextOffset,
|
offset: tabState.boostStatistics.nextOffset,
|
||||||
|
isGifts,
|
||||||
});
|
});
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
@ -634,17 +643,19 @@ addActionHandler('loadMoreBoosters', async (global, actions, payload): Promise<v
|
|||||||
tabState = selectTabState(global, tabId);
|
tabState = selectTabState(global, tabId);
|
||||||
if (!tabState.boostStatistics) return;
|
if (!tabState.boostStatistics) return;
|
||||||
|
|
||||||
|
const updatedBoostList = (isGifts
|
||||||
|
? tabState.boostStatistics.giftedBoosts?.list || []
|
||||||
|
: tabState.boostStatistics.boosts?.list || []).concat(result.boostList);
|
||||||
|
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
boostStatistics: {
|
boostStatistics: {
|
||||||
...tabState.boostStatistics,
|
...tabState.boostStatistics,
|
||||||
boosters: {
|
|
||||||
...tabState.boostStatistics.boosters,
|
|
||||||
...result.boosters,
|
|
||||||
},
|
|
||||||
boosterIds: unique([...tabState.boostStatistics.boosterIds || [], ...result.boosterIds]),
|
|
||||||
count: result.count,
|
|
||||||
nextOffset: result.nextOffset,
|
nextOffset: result.nextOffset,
|
||||||
isLoadingBoosters: false,
|
isLoadingBoosters: false,
|
||||||
|
[isGifts ? 'giftedBoosts' : 'boosts']: {
|
||||||
|
count: result.count,
|
||||||
|
list: updatedBoostList,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type {
|
|||||||
ApiAttachBot,
|
ApiAttachBot,
|
||||||
ApiAttachment,
|
ApiAttachment,
|
||||||
ApiAvailableReaction,
|
ApiAvailableReaction,
|
||||||
|
ApiBoost,
|
||||||
ApiBoostsStatus,
|
ApiBoostsStatus,
|
||||||
ApiChannelStatistics,
|
ApiChannelStatistics,
|
||||||
ApiChat,
|
ApiChat,
|
||||||
@ -694,12 +695,17 @@ export type TabState = {
|
|||||||
|
|
||||||
boostStatistics?: {
|
boostStatistics?: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
boosters?: Record<string, number>;
|
|
||||||
boosterIds?: string[];
|
|
||||||
boostStatus?: ApiBoostsStatus;
|
boostStatus?: ApiBoostsStatus;
|
||||||
isLoadingBoosters?: boolean;
|
isLoadingBoosters?: boolean;
|
||||||
nextOffset?: string;
|
nextOffset?: string;
|
||||||
count?: number;
|
boosts?: {
|
||||||
|
count: number;
|
||||||
|
list: ApiBoost[];
|
||||||
|
};
|
||||||
|
giftedBoosts?: {
|
||||||
|
count: number;
|
||||||
|
list: ApiBoost[];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
giftCodeModal?: {
|
giftCodeModal?: {
|
||||||
@ -2356,7 +2362,7 @@ export interface ActionPayloads {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
closeBoostStatistics: WithTabId | undefined;
|
closeBoostStatistics: WithTabId | undefined;
|
||||||
loadMoreBoosters: WithTabId | undefined;
|
loadMoreBoosters: { isGifts?: boolean } & WithTabId | undefined;
|
||||||
applyBoost: {
|
applyBoost: {
|
||||||
slots: number[];
|
slots: number[];
|
||||||
chatId: string;
|
chatId: string;
|
||||||
|
|||||||
@ -30,6 +30,8 @@ $color-error: #e53935;
|
|||||||
$color-warning: #fb8c00;
|
$color-warning: #fb8c00;
|
||||||
|
|
||||||
$color-yellow: #fdd764;
|
$color-yellow: #fdd764;
|
||||||
|
$color-orange: #d08a31;
|
||||||
|
$color-light-coral: #d08a3133;
|
||||||
|
|
||||||
$color-white: #ffffff;
|
$color-white: #ffffff;
|
||||||
$color-black: #000000;
|
$color-black: #000000;
|
||||||
@ -115,6 +117,9 @@ $color-message-story-mention-to: #74bcff;
|
|||||||
|
|
||||||
--color-yellow: #{$color-yellow};
|
--color-yellow: #{$color-yellow};
|
||||||
|
|
||||||
|
--color-orange: #{$color-orange};
|
||||||
|
--color-light-coral: #{$color-light-coral};
|
||||||
|
|
||||||
--color-links: #{$color-links};
|
--color-links: #{$color-links};
|
||||||
|
|
||||||
--color-own-links: #{$color-white};
|
--color-own-links: #{$color-white};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user