Boosts: Support admin view (#3912)
This commit is contained in:
parent
3e59a07012
commit
dd2bd5ddfe
@ -163,6 +163,8 @@ function buildStatisticsOverview({ current, previous }: GramJs.StatsAbsValueAndP
|
|||||||
|
|
||||||
export function buildStatisticsPercentage(data: GramJs.StatsPercentValue): StatisticsOverviewPercentage {
|
export function buildStatisticsPercentage(data: GramJs.StatsPercentValue): StatisticsOverviewPercentage {
|
||||||
return {
|
return {
|
||||||
|
part: data.part,
|
||||||
|
total: data.total,
|
||||||
percentage: ((data.part / data.total) * 100).toFixed(2),
|
percentage: ((data.part / data.total) * 100).toFixed(2),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,12 @@ import { Api as GramJs, errors } from '../../../lib/gramjs';
|
|||||||
import type {
|
import type {
|
||||||
ApiApplyBoostInfo,
|
ApiApplyBoostInfo,
|
||||||
ApiBoostsStatus,
|
ApiBoostsStatus,
|
||||||
ApiMediaArea, ApiMediaAreaCoordinates, ApiMessage, ApiStealthMode, ApiStoryView, ApiTypeStory,
|
ApiMediaArea,
|
||||||
|
ApiMediaAreaCoordinates,
|
||||||
|
ApiMessage,
|
||||||
|
ApiStealthMode,
|
||||||
|
ApiStoryView,
|
||||||
|
ApiTypeStory,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { buildCollectionByCallback } from '../../../util/iteratees';
|
import { buildCollectionByCallback } from '../../../util/iteratees';
|
||||||
@ -215,6 +220,6 @@ export function buildApiBoostsStatus(boostStatus: GramJs.stories.BoostsStatus):
|
|||||||
hasMyBoost: Boolean(myBoost),
|
hasMyBoost: Boolean(myBoost),
|
||||||
boostUrl,
|
boostUrl,
|
||||||
nextLevelBoosts,
|
nextLevelBoosts,
|
||||||
...(premiumAudience && { premiumAudience: buildStatisticsPercentage(premiumAudience) }),
|
...(premiumAudience && { premiumSubscribers: buildStatisticsPercentage(premiumAudience) }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -494,3 +494,40 @@ export async function fetchBoostsStatus({
|
|||||||
|
|
||||||
return buildApiBoostsStatus(result);
|
return buildApiBoostsStatus(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchBoostersList({
|
||||||
|
chat,
|
||||||
|
offset = '',
|
||||||
|
limit,
|
||||||
|
}: {
|
||||||
|
chat: ApiChat;
|
||||||
|
offset?: string;
|
||||||
|
limit?: number;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.stories.GetBoostersList({
|
||||||
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
addEntitiesToLocalDb(result.users);
|
||||||
|
|
||||||
|
const users = result.users.map(buildApiUser).filter(Boolean);
|
||||||
|
|
||||||
|
const boosterIds = result.boosters.map((booster) => booster.userId.toString());
|
||||||
|
const boosters = buildCollectionByCallback(result.boosters, (booster) => (
|
||||||
|
[booster.userId.toString(), booster.expires]
|
||||||
|
));
|
||||||
|
|
||||||
|
return {
|
||||||
|
count: result.count,
|
||||||
|
users,
|
||||||
|
boosters,
|
||||||
|
boosterIds,
|
||||||
|
nextOffset: result.nextOffset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -39,6 +39,13 @@ export interface ApiMessageStatistics {
|
|||||||
publicForwardsData?: ApiMessagePublicForward[];
|
publicForwardsData?: ApiMessagePublicForward[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiBoostStatistics {
|
||||||
|
level: number;
|
||||||
|
boosts: number;
|
||||||
|
premiumSubscribers: StatisticsOverviewPercentage;
|
||||||
|
remainingBoosts: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ApiMessagePublicForward {
|
export interface ApiMessagePublicForward {
|
||||||
messageId: number;
|
messageId: number;
|
||||||
views?: number;
|
views?: number;
|
||||||
@ -76,6 +83,8 @@ export interface StatisticsOverviewItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface StatisticsOverviewPercentage {
|
export interface StatisticsOverviewPercentage {
|
||||||
|
part: number;
|
||||||
|
total: number;
|
||||||
percentage: string;
|
percentage: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -137,5 +137,5 @@ export type ApiBoostsStatus = {
|
|||||||
nextLevelBoosts?: number;
|
nextLevelBoosts?: number;
|
||||||
hasMyBoost?: boolean;
|
hasMyBoost?: boolean;
|
||||||
boostUrl: string;
|
boostUrl: string;
|
||||||
premiumAudience?: StatisticsOverviewPercentage;
|
premiumSubscribers?: StatisticsOverviewPercentage;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,12 +17,7 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttons {
|
.title {
|
||||||
display: flex;
|
font-weight: 500;
|
||||||
gap: 1rem;
|
color: var(--color-text-secondary);
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
width: auto;
|
|
||||||
flex: 1 0 auto;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,15 +18,17 @@ import styles from './InviteLink.module.scss';
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
title?: string;
|
title?: string;
|
||||||
inviteLink: string;
|
inviteLink: string;
|
||||||
onRevoke?: VoidFunction;
|
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
|
className?: string;
|
||||||
|
onRevoke?: VoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InviteLink: FC<OwnProps> = ({
|
const InviteLink: FC<OwnProps> = ({
|
||||||
title,
|
title,
|
||||||
inviteLink,
|
inviteLink,
|
||||||
onRevoke,
|
|
||||||
isDisabled,
|
isDisabled,
|
||||||
|
className,
|
||||||
|
onRevoke,
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const { showNotification, openChatWithDraft } = getActions();
|
const { showNotification, openChatWithDraft } = getActions();
|
||||||
@ -66,8 +68,8 @@ const InviteLink: FC<OwnProps> = ({
|
|||||||
}, [isMobile, lang]);
|
}, [isMobile, lang]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-item">
|
<div className={className}>
|
||||||
<p className="text-muted">
|
<p className={styles.title}>
|
||||||
{lang(title || 'InviteLink.InviteLink')}
|
{lang(title || 'InviteLink.InviteLink')}
|
||||||
</p>
|
</p>
|
||||||
<div className={styles.primaryLink}>
|
<div className={styles.primaryLink}>
|
||||||
@ -88,24 +90,13 @@ const InviteLink: FC<OwnProps> = ({
|
|||||||
)}
|
)}
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.buttons}>
|
<Button
|
||||||
<Button
|
size="smaller"
|
||||||
onClick={handleCopyPrimaryClicked}
|
disabled={isDisabled}
|
||||||
className={styles.button}
|
onClick={handleShare}
|
||||||
size="smaller"
|
>
|
||||||
disabled={isDisabled}
|
{lang('FolderLinkScreen.LinkActionShare')}
|
||||||
>
|
</Button>
|
||||||
{lang('FolderLinkScreen.LinkActionCopy')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleShare}
|
|
||||||
className={styles.button}
|
|
||||||
size="smaller"
|
|
||||||
disabled={isDisabled}
|
|
||||||
>
|
|
||||||
{lang('FolderLinkScreen.LinkActionShare')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
|
@import "../../styles/mixins";
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
background-color: var(--color-background);
|
|
||||||
padding: 1.5rem 1.5rem 0;
|
padding: 1.5rem 1.5rem 0;
|
||||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
|
||||||
margin-bottom: 0.625rem;
|
margin-bottom: 0.625rem;
|
||||||
|
|
||||||
|
@include side-panel-section;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
|||||||
@ -36,16 +36,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.floating-badge {
|
.floating-badge-wrapper {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #ffffff;
|
|
||||||
position: relative;
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
background-color: #7E85FF;
|
|
||||||
animation: rotate-in 0.5s ease-in-out;
|
animation: rotate-in 0.5s ease-in-out;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
height: 2.6875rem;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes rotate-in {
|
@keyframes rotate-in {
|
||||||
@ -63,9 +59,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.floating-badge {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
background-color: #7E85FF;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.floating-badge-triangle {
|
.floating-badge-triangle {
|
||||||
|
display: inline-block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -15px;
|
bottom: -5px;
|
||||||
|
left: calc(var(--tail-position, 0.5) * 100%);
|
||||||
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.floating-badge-icon {
|
.floating-badge-icon {
|
||||||
|
|||||||
@ -21,6 +21,8 @@ type OwnProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PROGRESS_LOCK = 0.1;
|
||||||
|
|
||||||
const LimitPreview: FC<OwnProps> = ({
|
const LimitPreview: FC<OwnProps> = ({
|
||||||
leftText,
|
leftText,
|
||||||
rightText,
|
rightText,
|
||||||
@ -34,6 +36,8 @@ const LimitPreview: FC<OwnProps> = ({
|
|||||||
const hasFloatingBadge = Boolean(floatingBadgeIcon || floatingBadgeText);
|
const hasFloatingBadge = Boolean(floatingBadgeIcon || floatingBadgeText);
|
||||||
const isProgressFull = Boolean(progress) && progress > 0.99;
|
const isProgressFull = Boolean(progress) && progress > 0.99;
|
||||||
|
|
||||||
|
const tailPosition = progress && (progress < PROGRESS_LOCK ? 0 : progress > 1 - PROGRESS_LOCK ? 1 : 0.5);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
@ -41,18 +45,25 @@ const LimitPreview: FC<OwnProps> = ({
|
|||||||
hasFloatingBadge && styles.withBadge,
|
hasFloatingBadge && styles.withBadge,
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
style={buildStyle(progress !== undefined && `--progress: ${progress}`)}
|
style={buildStyle(
|
||||||
|
progress !== undefined && `--progress: ${progress}`,
|
||||||
|
tailPosition !== undefined && `--tail-position: ${tailPosition}`,
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{hasFloatingBadge && (
|
{hasFloatingBadge && (
|
||||||
<div className={styles.badgeContainer}>
|
<div className={styles.badgeContainer}>
|
||||||
<div className={styles.floatingBadge}>
|
<div className={styles.floatingBadgeWrapper}>
|
||||||
{floatingBadgeIcon && <Icon name={floatingBadgeIcon} className={styles.floatingBadgeIcon} />}
|
<div className={styles.floatingBadge}>
|
||||||
{floatingBadgeText && (
|
{floatingBadgeIcon && <Icon name={floatingBadgeIcon} className={styles.floatingBadgeIcon} />}
|
||||||
<div className={styles.floatingBadgeValue} dir={lang.isRtl ? 'rtl' : undefined}>{floatingBadgeText}</div>
|
{floatingBadgeText && (
|
||||||
)}
|
<div className={styles.floatingBadgeValue} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
{floatingBadgeText}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className={styles.floatingBadgeTriangle}>
|
<div className={styles.floatingBadgeTriangle}>
|
||||||
<svg width="26" height="9" viewBox="0 0 26 9" fill="none">
|
<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
|
||||||
<path d="M0 0H26H24.4853C22.894 0 21.3679 0.632141 20.2426 1.75736L14.4142 7.58579C13.6332 8.36684 12.3668 8.36683 11.5858 7.58579L5.75736 1.75736C4.63214 0.632139 3.10602 0 1.51472 0H0Z" fill="#7E85FF" />
|
<path d="m 28,4 v 9 c 0.0089,7.283278 -3.302215,5.319646 -6.750951,8.589815 l -5.8284,5.82843 c -0.781,0.78105 -2.0474,0.78104 -2.8284,0 L 6.7638083,21.589815 C 2.8288652,17.959047 0.04527024,20.332086 0,13 V 4 C 0,4 0.00150581,0.97697493 3,1 5.3786658,1.018266 22.594519,0.9142007 25,1 c 2.992326,0.1067311 3,3 3,3 z" fill="#7E85FF" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -43,6 +43,7 @@ type OwnProps = {
|
|||||||
noStatusOrTyping?: boolean;
|
noStatusOrTyping?: boolean;
|
||||||
noRtl?: boolean;
|
noRtl?: boolean;
|
||||||
adminMember?: ApiChatMember;
|
adminMember?: ApiChatMember;
|
||||||
|
className?: string;
|
||||||
onEmojiStatusClick?: NoneToVoidFunction;
|
onEmojiStatusClick?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,8 +76,9 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
areMessagesLoaded,
|
areMessagesLoaded,
|
||||||
adminMember,
|
adminMember,
|
||||||
ripple,
|
ripple,
|
||||||
onEmojiStatusClick,
|
className,
|
||||||
storyViewerOrigin,
|
storyViewerOrigin,
|
||||||
|
onEmojiStatusClick,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadFullUser,
|
loadFullUser,
|
||||||
@ -180,7 +182,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ChatInfo" dir={!noRtl && lang.isRtl ? 'rtl' : undefined}>
|
<div className={buildClassName('ChatInfo', className)} dir={!noRtl && lang.isRtl ? 'rtl' : undefined}>
|
||||||
<Avatar
|
<Avatar
|
||||||
key={user.id}
|
key={user.id}
|
||||||
size={avatarSize}
|
size={avatarSize}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { ApiBoostsStatus } from '../../../api/types';
|
import type { ApiBoostsStatus } from '../../../api/types';
|
||||||
|
|
||||||
export function getBoostProgressInfo(boostInfo: ApiBoostsStatus) {
|
export function getBoostProgressInfo(boostInfo: ApiBoostsStatus, freezeOnLevelUp?: boolean) {
|
||||||
const {
|
const {
|
||||||
level, boosts, currentLevelBoosts, nextLevelBoosts, hasMyBoost,
|
level, boosts, currentLevelBoosts, nextLevelBoosts, hasMyBoost,
|
||||||
} = boostInfo;
|
} = boostInfo;
|
||||||
@ -8,7 +8,7 @@ export function getBoostProgressInfo(boostInfo: ApiBoostsStatus) {
|
|||||||
const currentLevel = level;
|
const currentLevel = level;
|
||||||
const hasNextLevel = Boolean(nextLevelBoosts);
|
const hasNextLevel = Boolean(nextLevelBoosts);
|
||||||
|
|
||||||
const isJustUpgraded = boosts === currentLevelBoosts && hasMyBoost;
|
const isJustUpgraded = freezeOnLevelUp && boosts === currentLevelBoosts && hasMyBoost;
|
||||||
|
|
||||||
const levelProgress = (!nextLevelBoosts || isJustUpgraded) ? 1
|
const levelProgress = (!nextLevelBoosts || isJustUpgraded) ? 1
|
||||||
: (boosts - currentLevelBoosts) / (nextLevelBoosts - currentLevelBoosts);
|
: (boosts - currentLevelBoosts) / (nextLevelBoosts - currentLevelBoosts);
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@import "../../../styles/mixins";
|
||||||
|
|
||||||
#Settings {
|
#Settings {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
@ -78,10 +80,10 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 1.5rem 1rem;
|
padding: 0 1.5rem 1rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: var(--color-background);
|
|
||||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
|
||||||
margin-bottom: 0.625rem;
|
margin-bottom: 0.625rem;
|
||||||
|
|
||||||
|
@include side-panel-section;
|
||||||
|
|
||||||
&.no-border {
|
&.no-border {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
@ -107,20 +109,12 @@
|
|||||||
|
|
||||||
.settings-main-menu {
|
.settings-main-menu {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
background-color: var(--color-background);
|
@include side-panel-section;
|
||||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
|
||||||
border-bottom: 0.625rem solid var(--color-background-secondary);
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .ChatExtra {
|
> .ChatExtra {
|
||||||
padding: 0 0.5rem 0.3125rem;
|
padding: 0 0.5rem 0.3125rem;
|
||||||
margin: 0 -0.5rem 0.625rem;
|
margin: 0 -0.5rem 0.625rem;
|
||||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
@include side-panel-section;
|
||||||
border-bottom: 0.625rem solid var(--color-background-secondary);
|
|
||||||
|
|
||||||
.ListItem.narrow {
|
.ListItem.narrow {
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
@ -130,15 +124,8 @@
|
|||||||
|
|
||||||
.settings-item-simple,
|
.settings-item-simple,
|
||||||
.settings-item {
|
.settings-item {
|
||||||
background-color: var(--color-background);
|
|
||||||
padding: 1.5rem 1.5rem 1rem;
|
padding: 1.5rem 1.5rem 1rem;
|
||||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
@include side-panel-section;
|
||||||
border-bottom: 0.625rem solid var(--color-background-secondary);
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-item {
|
.settings-item {
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
|
@import "../../../styles/mixins";
|
||||||
|
|
||||||
.SettingsGeneralBackground {
|
.SettingsGeneralBackground {
|
||||||
.settings-wallpapers {
|
.settings-wallpapers {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
grid-auto-rows: 1fr;
|
grid-auto-rows: 1fr;
|
||||||
grid-gap: 0.0625rem;
|
grid-gap: 0.0625rem;
|
||||||
background-color: var(--color-background);
|
@include side-panel-section;
|
||||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Loading {
|
.Loading {
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@import "../../../styles/mixins";
|
||||||
|
|
||||||
.SettingsGeneralBackgroundColor {
|
.SettingsGeneralBackgroundColor {
|
||||||
&:not(.is-dragging) .handle {
|
&:not(.is-dragging) .handle {
|
||||||
transition: transform 300ms ease;
|
transition: transform 300ms ease;
|
||||||
@ -69,8 +71,7 @@
|
|||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
grid-auto-rows: 1fr;
|
grid-auto-rows: 1fr;
|
||||||
grid-gap: 0.0625rem;
|
grid-gap: 0.0625rem;
|
||||||
background-color: var(--color-background);
|
@include side-panel-section;
|
||||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.predefined-color {
|
.predefined-color {
|
||||||
|
|||||||
@ -160,6 +160,7 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<InviteLink
|
<InviteLink
|
||||||
|
className="settings-item"
|
||||||
inviteLink={!url ? lang('Loading') : url}
|
inviteLink={!url ? lang('Loading') : url}
|
||||||
onRevoke={handleRevoke}
|
onRevoke={handleRevoke}
|
||||||
isDisabled={!chatsCount || isTouched}
|
isDisabled={!chatsCount || isTouched}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { ManagementScreens } from '../../types';
|
|||||||
|
|
||||||
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
||||||
import {
|
import {
|
||||||
|
getHasAdminRight,
|
||||||
isChatBasicGroup, isChatChannel, isChatSuperGroup, isUserId,
|
isChatBasicGroup, isChatChannel, isChatSuperGroup, isUserId,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import {
|
import {
|
||||||
@ -63,6 +64,7 @@ interface StateProps {
|
|||||||
canCall?: boolean;
|
canCall?: boolean;
|
||||||
canMute?: boolean;
|
canMute?: boolean;
|
||||||
canViewStatistics?: boolean;
|
canViewStatistics?: boolean;
|
||||||
|
canViewBoosts?: boolean;
|
||||||
canLeave?: boolean;
|
canLeave?: boolean;
|
||||||
canEnterVoiceChat?: boolean;
|
canEnterVoiceChat?: boolean;
|
||||||
canCreateVoiceChat?: boolean;
|
canCreateVoiceChat?: boolean;
|
||||||
@ -95,6 +97,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
canCall,
|
canCall,
|
||||||
canMute,
|
canMute,
|
||||||
canViewStatistics,
|
canViewStatistics,
|
||||||
|
canViewBoosts,
|
||||||
canLeave,
|
canLeave,
|
||||||
canEnterVoiceChat,
|
canEnterVoiceChat,
|
||||||
canCreateVoiceChat,
|
canCreateVoiceChat,
|
||||||
@ -425,6 +428,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
canCall={canCall}
|
canCall={canCall}
|
||||||
canMute={canMute}
|
canMute={canMute}
|
||||||
canViewStatistics={canViewStatistics}
|
canViewStatistics={canViewStatistics}
|
||||||
|
canViewBoosts={canViewBoosts}
|
||||||
canLeave={canLeave}
|
canLeave={canLeave}
|
||||||
canEnterVoiceChat={canEnterVoiceChat}
|
canEnterVoiceChat={canEnterVoiceChat}
|
||||||
canCreateVoiceChat={canCreateVoiceChat}
|
canCreateVoiceChat={canCreateVoiceChat}
|
||||||
@ -486,6 +490,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const canCreateVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && !chat.isCallActive
|
const canCreateVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && !chat.isCallActive
|
||||||
&& (chat.adminRights?.manageCall || (chat.isCreator && isChatBasicGroup(chat)));
|
&& (chat.adminRights?.manageCall || (chat.isCreator && isChatBasicGroup(chat)));
|
||||||
const canViewStatistics = isMainThread && chatFullInfo?.canViewStatistics;
|
const canViewStatistics = isMainThread && chatFullInfo?.canViewStatistics;
|
||||||
|
const canViewBoosts = isMainThread && isChannel && (canViewStatistics || getHasAdminRight(chat, 'postStories'));
|
||||||
const pendingJoinRequests = isMainThread ? chatFullInfo?.requestsPending : undefined;
|
const pendingJoinRequests = isMainThread ? chatFullInfo?.requestsPending : undefined;
|
||||||
const shouldJoinToSend = Boolean(chat?.isNotJoined && chat.isJoinToSend);
|
const shouldJoinToSend = Boolean(chat?.isNotJoined && chat.isJoinToSend);
|
||||||
const shouldSendJoinRequest = Boolean(chat?.isNotJoined && chat.isJoinRequest);
|
const shouldSendJoinRequest = Boolean(chat?.isNotJoined && chat.isJoinRequest);
|
||||||
@ -505,6 +510,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canCall,
|
canCall,
|
||||||
canMute,
|
canMute,
|
||||||
canViewStatistics,
|
canViewStatistics,
|
||||||
|
canViewBoosts,
|
||||||
canLeave,
|
canLeave,
|
||||||
canEnterVoiceChat,
|
canEnterVoiceChat,
|
||||||
canCreateVoiceChat,
|
canCreateVoiceChat,
|
||||||
|
|||||||
@ -81,6 +81,7 @@ export type OwnProps = {
|
|||||||
canCall?: boolean;
|
canCall?: boolean;
|
||||||
canMute?: boolean;
|
canMute?: boolean;
|
||||||
canViewStatistics?: boolean;
|
canViewStatistics?: boolean;
|
||||||
|
canViewBoosts?: boolean;
|
||||||
withForumActions?: boolean;
|
withForumActions?: boolean;
|
||||||
canLeave?: boolean;
|
canLeave?: boolean;
|
||||||
canEnterVoiceChat?: boolean;
|
canEnterVoiceChat?: boolean;
|
||||||
@ -137,6 +138,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
canCall,
|
canCall,
|
||||||
canMute,
|
canMute,
|
||||||
canViewStatistics,
|
canViewStatistics,
|
||||||
|
canViewBoosts,
|
||||||
pendingJoinRequests,
|
pendingJoinRequests,
|
||||||
canLeave,
|
canLeave,
|
||||||
canEnterVoiceChat,
|
canEnterVoiceChat,
|
||||||
@ -174,6 +176,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
openAddContactDialog,
|
openAddContactDialog,
|
||||||
requestMasterAndRequestCall,
|
requestMasterAndRequestCall,
|
||||||
toggleStatistics,
|
toggleStatistics,
|
||||||
|
openBoostStatistics,
|
||||||
openGiftPremiumModal,
|
openGiftPremiumModal,
|
||||||
openChatWithInfo,
|
openChatWithInfo,
|
||||||
openCreateTopicPanel,
|
openCreateTopicPanel,
|
||||||
@ -332,6 +335,12 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
closeMenu();
|
closeMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleBoostClick = useLastCallback(() => {
|
||||||
|
openBoostStatistics({ chatId });
|
||||||
|
setShouldCloseFast(!isRightColumnShown);
|
||||||
|
closeMenu();
|
||||||
|
});
|
||||||
|
|
||||||
const handleEnableTranslations = useLastCallback(() => {
|
const handleEnableTranslations = useLastCallback(() => {
|
||||||
togglePeerTranslations({ chatId, isEnabled: true });
|
togglePeerTranslations({ chatId, isEnabled: true });
|
||||||
closeMenu();
|
closeMenu();
|
||||||
@ -546,6 +555,14 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
{lang('ReportSelectMessages')}
|
{lang('ReportSelectMessages')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
{canViewBoosts && (
|
||||||
|
<MenuItem
|
||||||
|
icon="boost"
|
||||||
|
onClick={handleBoostClick}
|
||||||
|
>
|
||||||
|
{lang('Boosts')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
{canViewStatistics && (
|
{canViewStatistics && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="stats"
|
icon="stats"
|
||||||
|
|||||||
@ -114,7 +114,7 @@ const BoostModal = ({
|
|||||||
hasNextLevel,
|
hasNextLevel,
|
||||||
levelProgress,
|
levelProgress,
|
||||||
remainingBoosts,
|
remainingBoosts,
|
||||||
} = getBoostProgressInfo(info.boostStatus);
|
} = getBoostProgressInfo(info.boostStatus, true);
|
||||||
|
|
||||||
const hasBoost = hasMyBoost || info.applyInfo?.type === 'already';
|
const hasBoost = hasMyBoost || info.applyInfo?.type === 'already';
|
||||||
const isJustUpgraded = boosts === currentLevelBoosts && hasBoost;
|
const isJustUpgraded = boosts === currentLevelBoosts && hasBoost;
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@import '../../styles/mixins';
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -17,9 +19,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
background-color: var(--color-background);
|
@include side-panel-section;
|
||||||
border-bottom: 0.625rem solid var(--color-background-secondary);
|
|
||||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.general {
|
.general {
|
||||||
|
|||||||
@ -18,8 +18,8 @@ import PollAnswerResults from './PollAnswerResults';
|
|||||||
import './PollResults.scss';
|
import './PollResults.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
onClose: NoneToVoidFunction;
|
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
onClose: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -28,10 +28,10 @@ type StateProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PollResults: FC<OwnProps & StateProps> = ({
|
const PollResults: FC<OwnProps & StateProps> = ({
|
||||||
onClose,
|
|
||||||
isActive,
|
isActive,
|
||||||
chat,
|
chat,
|
||||||
message,
|
message,
|
||||||
|
onClose,
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@import '../../styles/mixins';
|
||||||
|
|
||||||
.Profile {
|
.Profile {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -16,8 +18,7 @@
|
|||||||
|
|
||||||
> .profile-info > .ChatExtra {
|
> .profile-info > .ChatExtra {
|
||||||
padding: 0.875rem 0.5rem 0.5rem;
|
padding: 0.875rem 0.5rem 0.5rem;
|
||||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
@include side-panel-section;
|
||||||
border-bottom: 0.625rem solid var(--color-background-secondary);
|
|
||||||
|
|
||||||
.narrow {
|
.narrow {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import PollResults from './PollResults.async';
|
|||||||
import Profile from './Profile';
|
import Profile from './Profile';
|
||||||
import RightHeader from './RightHeader';
|
import RightHeader from './RightHeader';
|
||||||
import RightSearch from './RightSearch.async';
|
import RightSearch from './RightSearch.async';
|
||||||
|
import BoostStatistics from './statistics/BoostStatistics';
|
||||||
import MessageStatistics from './statistics/MessageStatistics.async';
|
import MessageStatistics from './statistics/MessageStatistics.async';
|
||||||
import Statistics from './statistics/Statistics.async';
|
import Statistics from './statistics/Statistics.async';
|
||||||
import StickerSearch from './StickerSearch.async';
|
import StickerSearch from './StickerSearch.async';
|
||||||
@ -90,6 +91,7 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
|||||||
resetNextProfileTab,
|
resetNextProfileTab,
|
||||||
closeCreateTopicPanel,
|
closeCreateTopicPanel,
|
||||||
closeEditTopicPanel,
|
closeEditTopicPanel,
|
||||||
|
closeBoostStatistics,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const { width: windowWidth } = useWindowSize();
|
const { width: windowWidth } = useWindowSize();
|
||||||
@ -105,6 +107,7 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
|||||||
const isManagement = contentKey === RightColumnContent.Management;
|
const isManagement = contentKey === RightColumnContent.Management;
|
||||||
const isStatistics = contentKey === RightColumnContent.Statistics;
|
const isStatistics = contentKey === RightColumnContent.Statistics;
|
||||||
const isMessageStatistics = contentKey === RightColumnContent.MessageStatistics;
|
const isMessageStatistics = contentKey === RightColumnContent.MessageStatistics;
|
||||||
|
const isBoostStatistics = contentKey === RightColumnContent.BoostStatistics;
|
||||||
const isStickerSearch = contentKey === RightColumnContent.StickerSearch;
|
const isStickerSearch = contentKey === RightColumnContent.StickerSearch;
|
||||||
const isGifSearch = contentKey === RightColumnContent.GifSearch;
|
const isGifSearch = contentKey === RightColumnContent.GifSearch;
|
||||||
const isPollResults = contentKey === RightColumnContent.PollResults;
|
const isPollResults = contentKey === RightColumnContent.PollResults;
|
||||||
@ -176,6 +179,9 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
|||||||
case RightColumnContent.Statistics:
|
case RightColumnContent.Statistics:
|
||||||
toggleStatistics();
|
toggleStatistics();
|
||||||
break;
|
break;
|
||||||
|
case RightColumnContent.BoostStatistics:
|
||||||
|
closeBoostStatistics();
|
||||||
|
break;
|
||||||
case RightColumnContent.Search: {
|
case RightColumnContent.Search: {
|
||||||
blurSearchInput();
|
blurSearchInput();
|
||||||
closeLocalTextSearch();
|
closeLocalTextSearch();
|
||||||
@ -312,6 +318,8 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
case RightColumnContent.Statistics:
|
case RightColumnContent.Statistics:
|
||||||
return <Statistics chatId={chatId!} />;
|
return <Statistics chatId={chatId!} />;
|
||||||
|
case RightColumnContent.BoostStatistics:
|
||||||
|
return <BoostStatistics />;
|
||||||
case RightColumnContent.MessageStatistics:
|
case RightColumnContent.MessageStatistics:
|
||||||
return <MessageStatistics chatId={chatId!} isActive={isOpen && isActive} />;
|
return <MessageStatistics chatId={chatId!} isActive={isOpen && isActive} />;
|
||||||
case RightColumnContent.StickerSearch:
|
case RightColumnContent.StickerSearch:
|
||||||
@ -346,6 +354,7 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
|||||||
isSearch={isSearch}
|
isSearch={isSearch}
|
||||||
isManagement={isManagement}
|
isManagement={isManagement}
|
||||||
isStatistics={isStatistics}
|
isStatistics={isStatistics}
|
||||||
|
isBoostStatistics={isBoostStatistics}
|
||||||
isMessageStatistics={isMessageStatistics}
|
isMessageStatistics={isMessageStatistics}
|
||||||
isStickerSearch={isStickerSearch}
|
isStickerSearch={isStickerSearch}
|
||||||
isGifSearch={isGifSearch}
|
isGifSearch={isGifSearch}
|
||||||
|
|||||||
@ -46,6 +46,7 @@ type OwnProps = {
|
|||||||
isSearch?: boolean;
|
isSearch?: boolean;
|
||||||
isManagement?: boolean;
|
isManagement?: boolean;
|
||||||
isStatistics?: boolean;
|
isStatistics?: boolean;
|
||||||
|
isBoostStatistics?: boolean;
|
||||||
isMessageStatistics?: boolean;
|
isMessageStatistics?: boolean;
|
||||||
isStickerSearch?: boolean;
|
isStickerSearch?: boolean;
|
||||||
isGifSearch?: boolean;
|
isGifSearch?: boolean;
|
||||||
@ -88,6 +89,7 @@ enum HeaderContent {
|
|||||||
Search,
|
Search,
|
||||||
Statistics,
|
Statistics,
|
||||||
MessageStatistics,
|
MessageStatistics,
|
||||||
|
BoostStatistics,
|
||||||
Management,
|
Management,
|
||||||
ManageInitial,
|
ManageInitial,
|
||||||
ManageChannelSubscribers,
|
ManageChannelSubscribers,
|
||||||
@ -126,6 +128,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
isManagement,
|
isManagement,
|
||||||
isStatistics,
|
isStatistics,
|
||||||
isMessageStatistics,
|
isMessageStatistics,
|
||||||
|
isBoostStatistics,
|
||||||
isStickerSearch,
|
isStickerSearch,
|
||||||
isGifSearch,
|
isGifSearch,
|
||||||
isPollResults,
|
isPollResults,
|
||||||
@ -139,8 +142,6 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
isSelf,
|
isSelf,
|
||||||
canManage,
|
canManage,
|
||||||
isChannel,
|
isChannel,
|
||||||
onClose,
|
|
||||||
onScreenSelect,
|
|
||||||
messageSearchQuery,
|
messageSearchQuery,
|
||||||
stickerSearchQuery,
|
stickerSearchQuery,
|
||||||
gifSearchQuery,
|
gifSearchQuery,
|
||||||
@ -151,6 +152,8 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
isBot,
|
isBot,
|
||||||
isInsideTopic,
|
isInsideTopic,
|
||||||
canEditTopic,
|
canEditTopic,
|
||||||
|
onClose,
|
||||||
|
onScreenSelect,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
setLocalTextSearchQuery,
|
setLocalTextSearchQuery,
|
||||||
@ -288,6 +291,8 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
HeaderContent.Statistics
|
HeaderContent.Statistics
|
||||||
) : isMessageStatistics ? (
|
) : isMessageStatistics ? (
|
||||||
HeaderContent.MessageStatistics
|
HeaderContent.MessageStatistics
|
||||||
|
) : isBoostStatistics ? (
|
||||||
|
HeaderContent.BoostStatistics
|
||||||
) : isCreatingTopic ? (
|
) : isCreatingTopic ? (
|
||||||
HeaderContent.CreateTopic
|
HeaderContent.CreateTopic
|
||||||
) : isEditingTopic ? (
|
) : isEditingTopic ? (
|
||||||
@ -437,6 +442,8 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
return <h3>{lang(isChannel ? 'ChannelStats.Title' : 'GroupStats.Title')}</h3>;
|
return <h3>{lang(isChannel ? 'ChannelStats.Title' : 'GroupStats.Title')}</h3>;
|
||||||
case HeaderContent.MessageStatistics:
|
case HeaderContent.MessageStatistics:
|
||||||
return <h3>{lang('Stats.MessageTitle')}</h3>;
|
return <h3>{lang('Stats.MessageTitle')}</h3>;
|
||||||
|
case HeaderContent.BoostStatistics:
|
||||||
|
return <h3>{lang('Boosts')}</h3>;
|
||||||
case HeaderContent.SharedMedia:
|
case HeaderContent.SharedMedia:
|
||||||
return <h3>{lang('SharedMedia')}</h3>;
|
return <h3>{lang('SharedMedia')}</h3>;
|
||||||
case HeaderContent.ManageChannelSubscribers:
|
case HeaderContent.ManageChannelSubscribers:
|
||||||
|
|||||||
@ -284,6 +284,7 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
{primaryInviteLink && (
|
{primaryInviteLink && (
|
||||||
<InviteLink
|
<InviteLink
|
||||||
|
className="section"
|
||||||
inviteLink={primaryInviteLink}
|
inviteLink={primaryInviteLink}
|
||||||
onRevoke={!chat?.usernames ? handlePrimaryRevoke : undefined}
|
onRevoke={!chat?.usernames ? handlePrimaryRevoke : undefined}
|
||||||
title={chat?.usernames ? lang('PublicLink') : lang('lng_create_permanent_link_title')}
|
title={chat?.usernames ? lang('PublicLink') : lang('lng_create_permanent_link_title')}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@import "../../../styles/mixins";
|
||||||
|
|
||||||
.Management {
|
.Management {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -15,14 +17,7 @@
|
|||||||
|
|
||||||
.section {
|
.section {
|
||||||
padding: 1rem 1.5rem;
|
padding: 1rem 1.5rem;
|
||||||
background-color: var(--color-background);
|
@include side-panel-section;
|
||||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
|
||||||
border-bottom: 0.625rem solid var(--color-background-secondary);
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.wide {
|
&.wide {
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
@ -168,10 +163,10 @@
|
|||||||
|
|
||||||
&__filter {
|
&__filter {
|
||||||
padding: 0 1rem 0.25rem 0.75rem;
|
padding: 0 1rem 0.25rem 0.75rem;
|
||||||
background-color: var(--color-background);
|
|
||||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
|
||||||
margin-bottom: 0.625rem;
|
margin-bottom: 0.625rem;
|
||||||
|
|
||||||
|
@include side-panel-section;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@ -341,10 +336,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.part {
|
.part {
|
||||||
background-color: var(--color-background);
|
|
||||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
|
||||||
margin: 0 -1.5rem;
|
margin: 0 -1.5rem;
|
||||||
padding: 0 1.5rem 1rem;
|
padding: 0 1.5rem 1rem;
|
||||||
|
|
||||||
|
@include side-panel-section;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section, .part {
|
.section, .part {
|
||||||
|
|||||||
40
src/components/right/statistics/BoostStatistics.module.scss
Normal file
40
src/components/right/statistics/BoostStatistics.module.scss
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
@import '../../../styles/mixins';
|
||||||
|
|
||||||
|
.root {
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noResults {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
padding: 1rem;
|
||||||
|
@include side-panel-section;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user :global(.status) {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
margin-bottom: 0;
|
||||||
|
border: none;
|
||||||
|
padding: 0.75rem 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.down {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadMoreSpinner {
|
||||||
|
margin-inline-end: 1rem;
|
||||||
|
}
|
||||||
154
src/components/right/statistics/BoostStatistics.tsx
Normal file
154
src/components/right/statistics/BoostStatistics.tsx
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiBoostStatistics } from '../../../api/types';
|
||||||
|
import type { TabState } from '../../../global/types';
|
||||||
|
|
||||||
|
import { selectTabState } from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { formatDateAtTime } from '../../../util/dateFormat';
|
||||||
|
import { getBoostProgressInfo } from '../../common/helpers/boostInfo';
|
||||||
|
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import Icon from '../../common/Icon';
|
||||||
|
import InviteLink from '../../common/InviteLink';
|
||||||
|
import PremiumProgress from '../../common/PremiumProgress';
|
||||||
|
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||||
|
import ListItem from '../../ui/ListItem';
|
||||||
|
import Loading from '../../ui/Loading';
|
||||||
|
import Spinner from '../../ui/Spinner';
|
||||||
|
import StatisticsOverview from './StatisticsOverview';
|
||||||
|
|
||||||
|
import styles from './BoostStatistics.module.scss';
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
boostStatistics: TabState['boostStatistics'];
|
||||||
|
};
|
||||||
|
|
||||||
|
const BoostStatistics = ({
|
||||||
|
boostStatistics,
|
||||||
|
}: StateProps) => {
|
||||||
|
const { openChat, loadMoreBoosters, closeBoostStatistics } = getActions();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const isLoaded = boostStatistics?.boostStatus;
|
||||||
|
const status = isLoaded ? boostStatistics.boostStatus : undefined;
|
||||||
|
|
||||||
|
const {
|
||||||
|
currentLevel,
|
||||||
|
hasNextLevel,
|
||||||
|
boosts,
|
||||||
|
levelProgress,
|
||||||
|
remainingBoosts,
|
||||||
|
} = useMemo(() => {
|
||||||
|
if (!status) {
|
||||||
|
return {
|
||||||
|
currentLevel: 0,
|
||||||
|
hasNextLevel: false,
|
||||||
|
boosts: 0,
|
||||||
|
levelProgress: 0,
|
||||||
|
remainingBoosts: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return getBoostProgressInfo(status);
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
|
const statsOverview = useMemo(() => {
|
||||||
|
if (!status) return undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
level: currentLevel,
|
||||||
|
boosts,
|
||||||
|
premiumSubscribers: status.premiumSubscribers!,
|
||||||
|
remainingBoosts,
|
||||||
|
} satisfies ApiBoostStatistics;
|
||||||
|
}, [status, boosts, currentLevel, remainingBoosts]);
|
||||||
|
|
||||||
|
const boostersToLoadCount = useMemo(() => {
|
||||||
|
if (!boostStatistics?.count) return undefined;
|
||||||
|
const loadedCount = boostStatistics.boosterIds?.length || 0;
|
||||||
|
const totalCount = boostStatistics.count;
|
||||||
|
return totalCount - loadedCount;
|
||||||
|
}, [boostStatistics]);
|
||||||
|
|
||||||
|
const handleBoosterClick = useLastCallback((userId: string) => {
|
||||||
|
openChat({ id: userId });
|
||||||
|
closeBoostStatistics();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleLoadMore = useLastCallback(() => {
|
||||||
|
loadMoreBoosters();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={buildClassName(styles.root, 'custom-scroll')}>
|
||||||
|
{!isLoaded && <Loading />}
|
||||||
|
{isLoaded && statsOverview && (
|
||||||
|
<>
|
||||||
|
<div className={styles.section}>
|
||||||
|
<PremiumProgress
|
||||||
|
leftText={lang('BoostsLevel', currentLevel!)}
|
||||||
|
rightText={hasNextLevel ? lang('BoostsLevel', currentLevel! + 1) : undefined}
|
||||||
|
progress={levelProgress}
|
||||||
|
floatingBadgeText={boosts.toString()}
|
||||||
|
floatingBadgeIcon="boost"
|
||||||
|
/>
|
||||||
|
<StatisticsOverview className={styles.stats} statistics={statsOverview} type="boost" />
|
||||||
|
</div>
|
||||||
|
<div className={styles.section}>
|
||||||
|
<h4 className={styles.sectionHeader} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
{lang('Boosters')}
|
||||||
|
</h4>
|
||||||
|
{!boostStatistics.boosterIds?.length && (
|
||||||
|
<div className={styles.noResults}>{lang('NoBoostersHint')}</div>
|
||||||
|
)}
|
||||||
|
{boostStatistics.boosterIds?.map((userId) => (
|
||||||
|
<ListItem
|
||||||
|
key={userId}
|
||||||
|
className="chat-item-clickable"
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => handleBoosterClick(userId)}
|
||||||
|
>
|
||||||
|
<PrivateChatInfo
|
||||||
|
className={styles.user}
|
||||||
|
forceShowSelf
|
||||||
|
userId={userId}
|
||||||
|
status={lang('BoostExpireOn', formatDateAtTime(lang, boostStatistics.boosters![userId] * 1000))}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
{Boolean(boostersToLoadCount) && (
|
||||||
|
<ListItem
|
||||||
|
key="load-more"
|
||||||
|
className={styles.showMore}
|
||||||
|
disabled={boostStatistics?.isLoadingBoosters}
|
||||||
|
onClick={handleLoadMore}
|
||||||
|
>
|
||||||
|
{boostStatistics?.isLoadingBoosters ? (
|
||||||
|
<Spinner className={styles.loadMoreSpinner} />
|
||||||
|
) : (
|
||||||
|
<Icon name="down" className={styles.down} />
|
||||||
|
)}
|
||||||
|
{lang('ShowVotes', boostersToLoadCount)}
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<InviteLink className={styles.section} inviteLink={status!.boostUrl} title={lang('LinkForBoosting')} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal(
|
||||||
|
(global): StateProps => {
|
||||||
|
const tabState = selectTabState(global);
|
||||||
|
const boostStatistics = tabState.boostStatistics;
|
||||||
|
|
||||||
|
return {
|
||||||
|
boostStatistics,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(BoostStatistics));
|
||||||
@ -150,7 +150,7 @@ const Statistics: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName('Statistics custom-scroll', isReady && 'ready')}>
|
<div className={buildClassName('Statistics custom-scroll', isReady && 'ready')}>
|
||||||
<StatisticsOverview statistics={statistics} isMessage />
|
<StatisticsOverview statistics={statistics} type="message" title={lang('StatisticOverview')} />
|
||||||
|
|
||||||
{!loadedCharts.current.length && <Loading />}
|
{!loadedCharts.current.length && <Loading />}
|
||||||
|
|
||||||
|
|||||||
@ -178,7 +178,11 @@ const Statistics: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName('Statistics custom-scroll', isReady && 'ready')}>
|
<div className={buildClassName('Statistics custom-scroll', isReady && 'ready')}>
|
||||||
<StatisticsOverview statistics={statistics} isGroup={isGroup} />
|
<StatisticsOverview
|
||||||
|
statistics={statistics}
|
||||||
|
type={isGroup ? 'group' : 'channel'}
|
||||||
|
title={lang('StatisticOverview')}
|
||||||
|
/>
|
||||||
|
|
||||||
{!loadedCharts.current.length && <Loading />}
|
{!loadedCharts.current.length && <Loading />}
|
||||||
|
|
||||||
|
|||||||
@ -42,7 +42,13 @@
|
|||||||
|
|
||||||
&-value {
|
&-value {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 1rem;
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-secondary-value {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-inline-start: 0.25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { FC } from '../../../lib/teact/teact';
|
|||||||
import React, { memo } from '../../../lib/teact/teact';
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
ApiBoostStatistics,
|
||||||
ApiChannelStatistics, ApiGroupStatistics, ApiMessageStatistics, StatisticsOverviewItem,
|
ApiChannelStatistics, ApiGroupStatistics, ApiMessageStatistics, StatisticsOverviewItem,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ type OverviewCell = {
|
|||||||
name: string;
|
name: string;
|
||||||
title: string;
|
title: string;
|
||||||
isPercentage?: boolean;
|
isPercentage?: boolean;
|
||||||
|
withAbsoluteValue?: boolean;
|
||||||
isPlain?: boolean;
|
isPlain?: boolean;
|
||||||
isApproximate?: boolean;
|
isApproximate?: boolean;
|
||||||
};
|
};
|
||||||
@ -55,13 +57,38 @@ const MESSAGE_OVERVIEW: OverviewCell[][] = [
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const BOOST_OVERVIEW: OverviewCell[][] = [
|
||||||
|
[
|
||||||
|
{ name: 'level', title: 'Stats.Boosts.Level', isPlain: true },
|
||||||
|
{
|
||||||
|
name: 'premiumSubscribers',
|
||||||
|
title: 'Stats.Boosts.PremiumSubscribers',
|
||||||
|
isPercentage: true,
|
||||||
|
isApproximate: true,
|
||||||
|
withAbsoluteValue: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ name: 'boosts', title: 'Stats.Boosts.ExistingBoosts', isPlain: true },
|
||||||
|
{ name: 'remainingBoosts', title: 'Stats.Boosts.BoostsToLevelUp', isPlain: true },
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
type StatisticsType = 'channel' | 'group' | 'message' | 'boost';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isGroup?: boolean;
|
type: StatisticsType;
|
||||||
isMessage?: boolean;
|
title?: string;
|
||||||
statistics: ApiChannelStatistics | ApiGroupStatistics | ApiMessageStatistics;
|
className?: string;
|
||||||
|
statistics: ApiChannelStatistics | ApiGroupStatistics | ApiMessageStatistics | ApiBoostStatistics;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StatisticsOverview: FC<OwnProps> = ({ isGroup, isMessage, statistics }) => {
|
const StatisticsOverview: FC<OwnProps> = ({
|
||||||
|
title,
|
||||||
|
type,
|
||||||
|
statistics,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const renderOverviewItemValue = ({ change, percentage }: StatisticsOverviewItem) => {
|
const renderOverviewItemValue = ({ change, percentage }: StatisticsOverviewItem) => {
|
||||||
@ -86,10 +113,17 @@ const StatisticsOverview: FC<OwnProps> = ({ isGroup, isMessage, statistics }) =>
|
|||||||
|
|
||||||
const { period } = (statistics as ApiGroupStatistics);
|
const { period } = (statistics as ApiGroupStatistics);
|
||||||
|
|
||||||
|
const schema = type === 'boost' ? BOOST_OVERVIEW : type === 'message' ? MESSAGE_OVERVIEW : type === 'group'
|
||||||
|
? GROUP_OVERVIEW : CHANNEL_OVERVIEW;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="StatisticsOverview">
|
<div className={buildClassName('StatisticsOverview', className)}>
|
||||||
<div className="StatisticsOverview__header">
|
<div className="StatisticsOverview__header">
|
||||||
<div className="StatisticsOverview__title">{lang('StatisticOverview')}</div>
|
{title && (
|
||||||
|
<div className="StatisticsOverview__title">
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{period && (
|
{period && (
|
||||||
<div className="StatisticsOverview__caption">
|
<div className="StatisticsOverview__caption">
|
||||||
@ -99,7 +133,7 @@ const StatisticsOverview: FC<OwnProps> = ({ isGroup, isMessage, statistics }) =>
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table className="StatisticsOverview__table">
|
<table className="StatisticsOverview__table">
|
||||||
{(isMessage ? MESSAGE_OVERVIEW : isGroup ? GROUP_OVERVIEW : CHANNEL_OVERVIEW).map((row) => (
|
{schema.map((row) => (
|
||||||
<tr>
|
<tr>
|
||||||
{row.map((cell: OverviewCell) => {
|
{row.map((cell: OverviewCell) => {
|
||||||
const field = (statistics as any)[cell.name];
|
const field = (statistics as any)[cell.name];
|
||||||
@ -108,7 +142,7 @@ const StatisticsOverview: FC<OwnProps> = ({ isGroup, isMessage, statistics }) =>
|
|||||||
return (
|
return (
|
||||||
<td className="StatisticsOverview__table-cell">
|
<td className="StatisticsOverview__table-cell">
|
||||||
<b className="StatisticsOverview__table-value">
|
<b className="StatisticsOverview__table-value">
|
||||||
{cell.isApproximate ? `≈${formatInteger(field)}` : formatInteger(field)}
|
{`${cell.isApproximate ? '≈' : ''}${formatInteger(field)}`}
|
||||||
</b>
|
</b>
|
||||||
<h3 className="StatisticsOverview__table-heading">{lang(cell.title)}</h3>
|
<h3 className="StatisticsOverview__table-heading">{lang(cell.title)}</h3>
|
||||||
</td>
|
</td>
|
||||||
@ -118,7 +152,14 @@ const StatisticsOverview: FC<OwnProps> = ({ isGroup, isMessage, statistics }) =>
|
|||||||
if (cell.isPercentage) {
|
if (cell.isPercentage) {
|
||||||
return (
|
return (
|
||||||
<td className="StatisticsOverview__table-cell">
|
<td className="StatisticsOverview__table-cell">
|
||||||
<b className="StatisticsOverview__table-value">{field.percentage}%</b>
|
{cell.withAbsoluteValue && (
|
||||||
|
<span className="StatisticsOverview__table-value">
|
||||||
|
{`${cell.isApproximate ? '≈' : ''}${formatInteger(field.part)}`}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className={`StatisticsOverview__table-${cell.withAbsoluteValue ? 'secondary-' : ''}value`}>
|
||||||
|
{field.percentage}%
|
||||||
|
</span>
|
||||||
<h3 className="StatisticsOverview__table-heading">{lang(cell.title)}</h3>
|
<h3 className="StatisticsOverview__table-heading">{lang(cell.title)}</h3>
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -82,7 +82,8 @@
|
|||||||
|
|
||||||
> .icon {
|
> .icon {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
margin-right: 2rem;
|
margin-inline-start: 0.125rem;
|
||||||
|
margin-inline-end: 1.25rem;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -489,13 +490,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[dir="rtl"] {
|
|
||||||
.ListItem-button > .icon {
|
|
||||||
margin-left: 2rem;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-item-ellipsis {
|
.list-item-ellipsis {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import type { ActionReturnType } from '../../types';
|
|||||||
|
|
||||||
import { DEBUG, PREVIEW_AVATAR_COUNT } from '../../../config';
|
import { DEBUG, PREVIEW_AVATAR_COUNT } from '../../../config';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
import { buildCollectionByKey, unique } from '../../../util/iteratees';
|
||||||
import { translate } from '../../../util/langProvider';
|
import { translate } from '../../../util/langProvider';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
@ -557,6 +557,91 @@ addActionHandler('openBoostModal', async (global, actions, payload): Promise<voi
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openBoostStatistics', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { chatId, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
if (!chat) return;
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostStatistics: {
|
||||||
|
chatId,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
const [boostersListResult, boostStatusResult] = await Promise.all([
|
||||||
|
callApi('fetchBoostersList', { chat }),
|
||||||
|
callApi('fetchBoostsStatus', { chat }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
if (!boostersListResult || !boostStatusResult) {
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostStatistics: undefined,
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = addUsers(global, buildCollectionByKey(boostersListResult.users, 'id'));
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostStatistics: {
|
||||||
|
chatId,
|
||||||
|
boostStatus: boostStatusResult,
|
||||||
|
boosters: boostersListResult.boosters,
|
||||||
|
boosterIds: boostersListResult.boosterIds,
|
||||||
|
count: boostersListResult.count,
|
||||||
|
nextOffset: boostersListResult.nextOffset,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('loadMoreBoosters', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
let tabState = selectTabState(global, tabId);
|
||||||
|
if (!tabState.boostStatistics) return;
|
||||||
|
|
||||||
|
const chat = selectChat(global, tabState.boostStatistics.chatId);
|
||||||
|
if (!chat) return;
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostStatistics: {
|
||||||
|
...tabState.boostStatistics,
|
||||||
|
isLoadingBoosters: true,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
const result = await callApi('fetchBoostersList', {
|
||||||
|
chat,
|
||||||
|
offset: tabState.boostStatistics.nextOffset,
|
||||||
|
});
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||||
|
|
||||||
|
tabState = selectTabState(global, tabId);
|
||||||
|
if (!tabState.boostStatistics) return;
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostStatistics: {
|
||||||
|
...tabState.boostStatistics,
|
||||||
|
boosters: {
|
||||||
|
...tabState.boostStatistics.boosters,
|
||||||
|
...result.boosters,
|
||||||
|
},
|
||||||
|
boosterIds: unique([...tabState.boostStatistics.boosterIds || [], ...result.boosterIds]),
|
||||||
|
count: result.count,
|
||||||
|
nextOffset: result.nextOffset,
|
||||||
|
isLoadingBoosters: false,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('applyBoost', async (global, actions, payload): Promise<void> => {
|
addActionHandler('applyBoost', async (global, actions, payload): Promise<void> => {
|
||||||
const { chatId, tabId = getCurrentTabId() } = payload;
|
const { chatId, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
|||||||
@ -55,6 +55,7 @@ addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
|
|||||||
|
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
isStatisticsShown: false,
|
isStatisticsShown: false,
|
||||||
|
boostStatistics: undefined,
|
||||||
contentToBeScheduled: undefined,
|
contentToBeScheduled: undefined,
|
||||||
...(id !== selectTabState(global, tabId).forwardMessages.toChatId && {
|
...(id !== selectTabState(global, tabId).forwardMessages.toChatId && {
|
||||||
forwardMessages: {},
|
forwardMessages: {},
|
||||||
|
|||||||
@ -419,3 +419,11 @@ addActionHandler('closeBoostModal', (global, actions, payload): ActionReturnType
|
|||||||
boostModal: undefined,
|
boostModal: undefined,
|
||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeBoostStatistics', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
boostStatistics: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|||||||
@ -39,6 +39,8 @@ export function selectRightColumnContentKey<T extends GlobalState>(
|
|||||||
RightColumnContent.MessageStatistics
|
RightColumnContent.MessageStatistics
|
||||||
) : selectIsStatisticsShown(global, tabId) ? (
|
) : selectIsStatisticsShown(global, tabId) ? (
|
||||||
RightColumnContent.Statistics
|
RightColumnContent.Statistics
|
||||||
|
) : tabState.boostStatistics ? (
|
||||||
|
RightColumnContent.BoostStatistics
|
||||||
) : tabState.stickerSearch.query !== undefined ? (
|
) : tabState.stickerSearch.query !== undefined ? (
|
||||||
RightColumnContent.StickerSearch
|
RightColumnContent.StickerSearch
|
||||||
) : tabState.gifSearch.query !== undefined ? (
|
) : tabState.gifSearch.query !== undefined ? (
|
||||||
|
|||||||
@ -625,6 +625,16 @@ export type TabState = {
|
|||||||
boostStatus?: ApiBoostsStatus;
|
boostStatus?: ApiBoostsStatus;
|
||||||
applyInfo?: ApiApplyBoostInfo;
|
applyInfo?: ApiApplyBoostInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
boostStatistics?: {
|
||||||
|
chatId: string;
|
||||||
|
boosters?: Record<string, number>;
|
||||||
|
boosterIds?: string[];
|
||||||
|
boostStatus?: ApiBoostsStatus;
|
||||||
|
isLoadingBoosters?: boolean;
|
||||||
|
nextOffset?: string;
|
||||||
|
count?: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GlobalState = {
|
export type GlobalState = {
|
||||||
@ -2084,6 +2094,11 @@ export interface ActionPayloads {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
closeBoostModal: WithTabId | undefined;
|
closeBoostModal: WithTabId | undefined;
|
||||||
|
openBoostStatistics: {
|
||||||
|
chatId: string;
|
||||||
|
} & WithTabId;
|
||||||
|
closeBoostStatistics: WithTabId | undefined;
|
||||||
|
loadMoreBoosters: WithTabId | undefined;
|
||||||
applyBoost: {
|
applyBoost: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|||||||
6
src/lib/gramjs/tl/api.d.ts
vendored
6
src/lib/gramjs/tl/api.d.ts
vendored
@ -348,7 +348,7 @@ namespace Api {
|
|||||||
export type TypeAccessPointRule = AccessPointRule;
|
export type TypeAccessPointRule = AccessPointRule;
|
||||||
export type TypeTlsClientHello = TlsClientHello;
|
export type TypeTlsClientHello = TlsClientHello;
|
||||||
export type TypeTlsBlock = TlsBlockString | TlsBlockRandom | TlsBlockZero | TlsBlockDomain | TlsBlockGrease | TlsBlockScope;
|
export type TypeTlsBlock = TlsBlockString | TlsBlockRandom | TlsBlockZero | TlsBlockDomain | TlsBlockGrease | TlsBlockScope;
|
||||||
|
|
||||||
|
|
||||||
export namespace storage {
|
export namespace storage {
|
||||||
export type TypeFileType = storage.FileUnknown | storage.FilePartial | storage.FileJpeg | storage.FileGif | storage.FilePng | storage.FilePdf | storage.FileMp3 | storage.FileMov | storage.FileMp4 | storage.FileWebp;
|
export type TypeFileType = storage.FileUnknown | storage.FilePartial | storage.FileJpeg | storage.FileGif | storage.FilePng | storage.FilePdf | storage.FileMp3 | storage.FileMov | storage.FileMp4 | storage.FileWebp;
|
||||||
@ -8916,7 +8916,7 @@ namespace Api {
|
|||||||
}> {
|
}> {
|
||||||
entries: Api.TypeTlsBlock[];
|
entries: Api.TypeTlsBlock[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export namespace storage {
|
export namespace storage {
|
||||||
export class FileUnknown extends VirtualClass<void> {};
|
export class FileUnknown extends VirtualClass<void> {};
|
||||||
@ -10983,7 +10983,7 @@ namespace Api {
|
|||||||
}>, Api.TypeDestroySessionRes> {
|
}>, Api.TypeDestroySessionRes> {
|
||||||
sessionId: long;
|
sessionId: long;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export namespace auth {
|
export namespace auth {
|
||||||
export class SendCode extends Request<Partial<{
|
export class SendCode extends Request<Partial<{
|
||||||
|
|||||||
@ -1473,4 +1473,4 @@ stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool;
|
|||||||
stories.getBoostsStatus#4c449472 peer:InputPeer = stories.BoostsStatus;
|
stories.getBoostsStatus#4c449472 peer:InputPeer = stories.BoostsStatus;
|
||||||
stories.getBoostersList#337ef980 peer:InputPeer offset:string limit:int = stories.BoostersList;
|
stories.getBoostersList#337ef980 peer:InputPeer offset:string limit:int = stories.BoostersList;
|
||||||
stories.canApplyBoost#db05c1bd peer:InputPeer = stories.CanApplyBoostResult;
|
stories.canApplyBoost#db05c1bd peer:InputPeer = stories.CanApplyBoostResult;
|
||||||
stories.applyBoost#f29d7c2b peer:InputPeer = Bool;`;
|
stories.applyBoost#f29d7c2b peer:InputPeer = Bool;`;
|
||||||
@ -98,3 +98,14 @@
|
|||||||
box-shadow: 0 0.125rem 0.125rem var(--color-light-shadow);
|
box-shadow: 0 0.125rem 0.125rem var(--color-light-shadow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin side-panel-section {
|
||||||
|
background-color: var(--color-background);
|
||||||
|
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
||||||
|
border-bottom: 0.625rem solid var(--color-background-secondary);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -275,6 +275,7 @@ export enum RightColumnContent {
|
|||||||
Search,
|
Search,
|
||||||
Management,
|
Management,
|
||||||
Statistics,
|
Statistics,
|
||||||
|
BoostStatistics,
|
||||||
MessageStatistics,
|
MessageStatistics,
|
||||||
StickerSearch,
|
StickerSearch,
|
||||||
GifSearch,
|
GifSearch,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user