Manage Group Permissions: Implement paid messages settings for groups (#5845)
This commit is contained in:
parent
752c6f4fab
commit
888e740b39
@ -623,6 +623,7 @@ async function getFullChannelInfo(
|
||||
hasScheduled,
|
||||
stargiftsCount,
|
||||
stargiftsAvailable,
|
||||
paidMessagesAvailable,
|
||||
} = result.fullChat;
|
||||
|
||||
if (chatPhoto) {
|
||||
@ -717,6 +718,7 @@ async function getFullChannelInfo(
|
||||
hasScheduledMessages: hasScheduled,
|
||||
starGiftCount: stargiftsCount,
|
||||
areStarGiftsAvailable: Boolean(stargiftsAvailable),
|
||||
arePaidMessagesAvailable: paidMessagesAvailable,
|
||||
},
|
||||
chats,
|
||||
userStatusesById: statusesById,
|
||||
@ -2022,6 +2024,19 @@ export async function fetchChannelRecommendations({ chat }: { chat?: ApiChat })
|
||||
};
|
||||
}
|
||||
|
||||
export async function updatePaidMessagesPrice({
|
||||
chat, paidMessagesStars,
|
||||
}: {
|
||||
chat?: ApiChat; paidMessagesStars: number;
|
||||
}) {
|
||||
return invokeRequest(new GramJs.channels.UpdatePaidMessagesPrice({
|
||||
channel: chat && buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||
sendPaidMessagesStars: BigInt(paidMessagesStars),
|
||||
}), {
|
||||
shouldReturnTrue: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchSponsoredPeer({ query }: { query: string }) {
|
||||
const result = await invokeRequest(new GramJs.contacts.GetSponsoredPeers({ q: query }));
|
||||
if (!result || result instanceof GramJs.contacts.SponsoredPeersEmpty) return undefined;
|
||||
|
||||
@ -142,6 +142,7 @@ export interface ApiChatFullInfo {
|
||||
hasScheduledMessages?: boolean;
|
||||
starGiftCount?: number;
|
||||
areStarGiftsAvailable?: boolean;
|
||||
arePaidMessagesAvailable?: true;
|
||||
|
||||
boostsApplied?: number;
|
||||
boostsToUnrestrict?: number;
|
||||
|
||||
@ -1894,7 +1894,7 @@
|
||||
"ExceptionTitlePrivacyChargeForMessages" = "Remove fee";
|
||||
"ExceptionDescriptionPrivacyChargeForMessages" = "Add users or entire groups who won't be charged for sending messages to you.";
|
||||
"SectionTitleStarsForForMessages" = "Set your price per message";
|
||||
"SectionDescriptionStarsForForMessages" = "You will receive {percent}% of the selected fee (~{amount}) for each incoming message.";
|
||||
"SectionDescriptionStarsForForMessages" = "You will receive {percent} of the selected fee (~{amount}) for each incoming message.";
|
||||
"SubtitlePrivacyAddUsers" = "Add Users";
|
||||
"PrivacyPaidMessagesValue" = "Paid";
|
||||
"FirstMessageInPaidMessagesChat" = "**{user}** charges {amount} for each message.";
|
||||
@ -1931,6 +1931,9 @@
|
||||
"DescriptionRestrictedMedia" = "Posting media content is not allowed in this group.";
|
||||
"DescriptionScheduledPaidMediaNotAllowed" = "Posting scheduled paid media content is not allowed";
|
||||
"DescriptionScheduledPaidMessagesNotAllowed" = "Scheduled paid messages is not allowed";
|
||||
"GroupMessagesChargePrice" = "Charge Stars for Messages";
|
||||
"RightsChargeStarsAbout" = "If you turn this on, regular members of the group will have to pay Stars to send messages.";
|
||||
"SetPriceGroupDescription" = "Your group will receive {percent} of the selected fee (~{amount}) for each incoming messages.";
|
||||
"UnlockButtonTitle" = "Unlock with Telegram Premium";
|
||||
"FrozenAccountModalTitle" = "Your Account is Frozen";
|
||||
"FrozenAccountViolationTitle" = "Violation of Terms";
|
||||
|
||||
115
src/components/common/paidMessage/PaidMessagePrice.tsx
Normal file
115
src/components/common/paidMessage/PaidMessagePrice.tsx
Normal file
@ -0,0 +1,115 @@
|
||||
import React, {
|
||||
memo,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import {
|
||||
DEFAULT_MAXIMUM_CHARGE_FOR_MESSAGES,
|
||||
MINIMUM_CHARGE_FOR_MESSAGES,
|
||||
} from '../../../config';
|
||||
import { formatCurrencyAsString } from '../../../util/formatCurrency';
|
||||
import { formatPercent } from '../../../util/textFormat';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Button from '../../ui/Button';
|
||||
import Icon from '../icons/Icon';
|
||||
import PaidMessageSlider from './PaidMessageSlider';
|
||||
|
||||
type OwnProps = {
|
||||
chargeForMessages: number;
|
||||
canChangeChargeForMessages?: boolean;
|
||||
isGroupChat?: boolean;
|
||||
onChange: (value: number) => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
starsUsdWithdrawRate: number;
|
||||
starsPaidMessageAmountMax: number;
|
||||
starsPaidMessageCommissionPermille: number;
|
||||
};
|
||||
|
||||
function PaidMessagePrice({
|
||||
starsUsdWithdrawRate,
|
||||
starsPaidMessageAmountMax,
|
||||
starsPaidMessageCommissionPermille,
|
||||
canChangeChargeForMessages,
|
||||
isGroupChat,
|
||||
chargeForMessages,
|
||||
onChange,
|
||||
}: OwnProps & StateProps) {
|
||||
const { openPremiumModal } = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const handleChargeForMessagesChange = useLastCallback((value: number) => {
|
||||
onChange?.(value);
|
||||
});
|
||||
|
||||
const handleUnlockWithPremium = useLastCallback(() => {
|
||||
openPremiumModal({ initialSection: 'message_privacy' });
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
{lang('SectionTitleStarsForForMessages')}
|
||||
</h4>
|
||||
<PaidMessageSlider
|
||||
defaultValue={chargeForMessages}
|
||||
min={MINIMUM_CHARGE_FOR_MESSAGES}
|
||||
max={starsPaidMessageAmountMax}
|
||||
value={chargeForMessages}
|
||||
onChange={handleChargeForMessagesChange}
|
||||
canChangeChargeForMessages={canChangeChargeForMessages}
|
||||
readOnly={!canChangeChargeForMessages}
|
||||
/>
|
||||
{!canChangeChargeForMessages && (
|
||||
<Button
|
||||
color="primary"
|
||||
fluid
|
||||
size="smaller"
|
||||
noForcedUpperCase
|
||||
className="settings-unlock-button"
|
||||
onClick={handleUnlockWithPremium}
|
||||
>
|
||||
<span className="settings-unlock-button-title">
|
||||
{lang('UnlockButtonTitle')}
|
||||
<Icon name="lock-badge" className="settings-unlock-button-icon" />
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
{canChangeChargeForMessages && (
|
||||
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
{lang(isGroupChat ? 'SetPriceGroupDescription' : 'SectionDescriptionStarsForForMessages', {
|
||||
percent: formatPercent(starsPaidMessageCommissionPermille * 100),
|
||||
amount: formatCurrencyAsString(
|
||||
chargeForMessages * starsUsdWithdrawRate * starsPaidMessageCommissionPermille,
|
||||
'USD',
|
||||
lang.code,
|
||||
),
|
||||
}, {
|
||||
withNodes: true,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const starsUsdWithdrawRateX1000 = global.appConfig?.starsUsdWithdrawRateX1000;
|
||||
const starsUsdWithdrawRate = starsUsdWithdrawRateX1000 ? starsUsdWithdrawRateX1000 / 1000 : 1;
|
||||
const configStarsPaidMessageCommissionPermille = global.appConfig?.starsPaidMessageCommissionPermille;
|
||||
const starsPaidMessageCommissionPermille = configStarsPaidMessageCommissionPermille
|
||||
? configStarsPaidMessageCommissionPermille / 1000 : 100;
|
||||
|
||||
return {
|
||||
starsPaidMessageCommissionPermille,
|
||||
starsUsdWithdrawRate,
|
||||
starsPaidMessageAmountMax: global.appConfig?.starsPaidMessageAmountMax || DEFAULT_MAXIMUM_CHARGE_FOR_MESSAGES,
|
||||
};
|
||||
},
|
||||
)(PaidMessagePrice));
|
||||
121
src/components/common/paidMessage/PaidMessageSlider.tsx
Normal file
121
src/components/common/paidMessage/PaidMessageSlider.tsx
Normal file
@ -0,0 +1,121 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatStarsAsText } from '../../../util/localization/format';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Icon from '../icons/Icon';
|
||||
|
||||
type OwnProps = {
|
||||
min?: number;
|
||||
max: number;
|
||||
value: number;
|
||||
disabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
bold?: boolean;
|
||||
className?: string;
|
||||
defaultValue: number;
|
||||
onChange: (value: number) => void;
|
||||
canChangeChargeForMessages?: boolean;
|
||||
};
|
||||
|
||||
const DEFAULT_POINTS = [50, 100, 500, 1000, 2000, 5000, 10000];
|
||||
|
||||
const PaidMessageSlider: FC<OwnProps> = ({
|
||||
min = 0,
|
||||
max,
|
||||
value,
|
||||
disabled,
|
||||
readOnly,
|
||||
bold,
|
||||
className,
|
||||
defaultValue,
|
||||
onChange,
|
||||
canChangeChargeForMessages,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
const points = useMemo(() => {
|
||||
const result = [];
|
||||
for (let i = 0; i < DEFAULT_POINTS.length; i++) {
|
||||
if (DEFAULT_POINTS[i] < max) {
|
||||
result.push(DEFAULT_POINTS[i]);
|
||||
}
|
||||
|
||||
if (DEFAULT_POINTS[i] >= max) {
|
||||
result.push(max);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [max]);
|
||||
|
||||
const handleChange = useLastCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = Number(event.currentTarget.value);
|
||||
onChange(getValue(points, newValue));
|
||||
});
|
||||
|
||||
const mainClassName = buildClassName(
|
||||
className,
|
||||
'RangeSlider',
|
||||
disabled && 'disabled',
|
||||
readOnly && 'readOnly',
|
||||
bold && 'bold',
|
||||
);
|
||||
|
||||
function renderTopRow() {
|
||||
return (
|
||||
<div className="slider-top-row" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<span className="value-min" dir="auto">{lang.number(min)}</span>
|
||||
<span className="settings-range-value">
|
||||
{!canChangeChargeForMessages && (<Icon name="lock-badge" />)}
|
||||
{formatStarsAsText(lang, getValue(points, getProgress(points, value)))}
|
||||
</span>
|
||||
<span className="value-max" dir="auto">{lang.number(max)}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={mainClassName}>
|
||||
{renderTopRow()}
|
||||
<div className="slider-main">
|
||||
<div
|
||||
className="slider-fill-track"
|
||||
style={`width: ${(getProgress(points, value) / points.length) * 100}%`}
|
||||
/>
|
||||
<input
|
||||
min={0}
|
||||
max={points.length}
|
||||
defaultValue={getProgress(points, defaultValue)}
|
||||
step="any"
|
||||
type="range"
|
||||
className="RangeSlider__input"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function getProgress(points: number[], value: number) {
|
||||
const pointIndex = points.findIndex((point) => value <= point);
|
||||
const prevPoint = points[pointIndex - 1] || 1;
|
||||
const nextPoint = points[pointIndex] || points[points.length - 1];
|
||||
const progress = (value - prevPoint) / (nextPoint - prevPoint);
|
||||
return pointIndex + progress;
|
||||
}
|
||||
|
||||
function getValue(points: number[], progress: number) {
|
||||
const pointIndex = Math.floor(progress);
|
||||
const prevPoint = points[pointIndex - 1] || 1;
|
||||
const nextPoint = points[pointIndex] || points[points.length - 1];
|
||||
const pointValue = prevPoint + (nextPoint - prevPoint) * (progress - pointIndex);
|
||||
return pointValue < 100 ? Math.round(pointValue) : Math.round(pointValue / 10) * 10;
|
||||
}
|
||||
|
||||
export default memo(PaidMessageSlider);
|
||||
@ -7,16 +7,12 @@ import { SettingsScreens } from '../../../types';
|
||||
|
||||
import {
|
||||
DEFAULT_CHARGE_FOR_MESSAGES,
|
||||
DEFAULT_MAXIMUM_CHARGE_FOR_MESSAGES,
|
||||
MINIMUM_CHARGE_FOR_MESSAGES,
|
||||
} from '../../../config';
|
||||
import {
|
||||
selectIsCurrentUserPremium,
|
||||
selectNewNoncontactPeersRequirePremium,
|
||||
selectNonContactPeersPaidStars,
|
||||
} from '../../../global/selectors';
|
||||
import { formatCurrencyAsString } from '../../../util/formatCurrency';
|
||||
import { formatStarsAsText } from '../../../util/localization/format';
|
||||
|
||||
import useDebouncedCallback from '../../../hooks/useDebouncedCallback';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
@ -24,11 +20,9 @@ import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import Button from '../../ui/Button';
|
||||
import PaidMessagePrice from '../../common/paidMessage/PaidMessagePrice';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import RadioGroup from '../../ui/RadioGroup';
|
||||
import RangeSlider from '../../ui/RangeSlider';
|
||||
import PremiumStatusItem from './PremiumStatusItem';
|
||||
import PrivacyLockedOption from './PrivacyLockedOption';
|
||||
|
||||
@ -44,9 +38,6 @@ type StateProps = {
|
||||
canLimitNewMessagesWithoutPremium?: boolean;
|
||||
canChargeForMessages?: boolean;
|
||||
isCurrentUserPremium?: boolean;
|
||||
starsUsdWithdrawRate: number;
|
||||
starsPaidMessageCommissionPermille: number;
|
||||
starsPaidMessageAmountMax?: number;
|
||||
nonContactPeersPaidStars: number;
|
||||
noPaidReactionsForUsersCount: number;
|
||||
};
|
||||
@ -59,14 +50,11 @@ function PrivacyMessages({
|
||||
shouldChargeForMessages,
|
||||
nonContactPeersPaidStars,
|
||||
isCurrentUserPremium,
|
||||
starsPaidMessageCommissionPermille,
|
||||
starsPaidMessageAmountMax,
|
||||
starsUsdWithdrawRate,
|
||||
noPaidReactionsForUsersCount,
|
||||
onReset,
|
||||
onScreenSelect,
|
||||
}: OwnProps & StateProps) {
|
||||
const { updateGlobalPrivacySettings, openPremiumModal } = getActions();
|
||||
const { updateGlobalPrivacySettings } = getActions();
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
|
||||
@ -131,68 +119,6 @@ function PrivacyMessages({
|
||||
updateGlobalPrivacySettingsWithDebounced(value);
|
||||
}, [setChargeForMessages, updateGlobalPrivacySettingsWithDebounced]);
|
||||
|
||||
const renderValueForStarsRange = useCallback((value: number) => {
|
||||
return (
|
||||
<span className="settings-range-value">
|
||||
{!canChangeChargeForMessages && (<Icon name="lock-badge" />)}
|
||||
{formatStarsAsText(lang, value)}
|
||||
</span>
|
||||
);
|
||||
}, [lang, canChangeChargeForMessages]);
|
||||
|
||||
const handleUnlockWithPremium = useLastCallback(() => {
|
||||
openPremiumModal({ initialSection: 'message_privacy' });
|
||||
});
|
||||
|
||||
function renderSectionStarsAmountForPaidMessages() {
|
||||
return (
|
||||
<div className="settings-item fluid-container">
|
||||
<h4 className="settings-item-header" dir={oldLang.isRtl ? 'rtl' : undefined}>
|
||||
{lang('SectionTitleStarsForForMessages')}
|
||||
</h4>
|
||||
<RangeSlider
|
||||
isCenteredLayout
|
||||
min={MINIMUM_CHARGE_FOR_MESSAGES}
|
||||
max={starsPaidMessageAmountMax}
|
||||
value={chargeForMessages}
|
||||
onChange={handleChargeForMessagesChange}
|
||||
renderValue={renderValueForStarsRange}
|
||||
readOnly={!canChangeChargeForMessages}
|
||||
/>
|
||||
{!isCurrentUserPremium && (
|
||||
<Button
|
||||
color="primary"
|
||||
fluid
|
||||
size="smaller"
|
||||
noForcedUpperCase
|
||||
className="settings-unlock-button"
|
||||
onClick={handleUnlockWithPremium}
|
||||
>
|
||||
|
||||
<span className="settings-unlock-button-title">
|
||||
{lang('UnlockButtonTitle')}
|
||||
<Icon name="lock-badge" className="settings-unlock-button-icon" />
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
{isCurrentUserPremium && (
|
||||
<p className="settings-item-description-larger" dir={oldLang.isRtl ? 'rtl' : undefined}>
|
||||
{lang('SectionDescriptionStarsForForMessages', {
|
||||
percent: starsPaidMessageCommissionPermille * 100,
|
||||
amount: formatCurrencyAsString(
|
||||
chargeForMessages * starsUsdWithdrawRate * starsPaidMessageCommissionPermille,
|
||||
'USD',
|
||||
lang.code,
|
||||
),
|
||||
}, {
|
||||
withNodes: true,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderSectionNoPaidMessagesForUsers() {
|
||||
const itemSubtitle = !noPaidReactionsForUsersCount ? lang('SubtitlePrivacyAddUsers')
|
||||
: oldLang('Users', noPaidReactionsForUsersCount, 'i');
|
||||
@ -248,7 +174,15 @@ function PrivacyMessages({
|
||||
{privacyDescription}
|
||||
</p>
|
||||
</div>
|
||||
{selectedValue === 'charge_for_messages' && renderSectionStarsAmountForPaidMessages()}
|
||||
{selectedValue === 'charge_for_messages' && (
|
||||
<div className="settings-item fluid-container">
|
||||
<PaidMessagePrice
|
||||
canChangeChargeForMessages={canChangeChargeForMessages}
|
||||
chargeForMessages={chargeForMessages}
|
||||
onChange={handleChargeForMessagesChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{canChangeChargeForMessages && selectedValue === 'charge_for_messages' && renderSectionNoPaidMessagesForUsers()}
|
||||
{!isCurrentUserPremium && selectedValue !== 'charge_for_messages'
|
||||
&& <PremiumStatusItem premiumSection="message_privacy" />}
|
||||
@ -259,12 +193,6 @@ function PrivacyMessages({
|
||||
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
const nonContactPeersPaidStars = selectNonContactPeersPaidStars(global);
|
||||
|
||||
const starsUsdWithdrawRateX1000 = global.appConfig?.starsUsdWithdrawRateX1000;
|
||||
const starsUsdWithdrawRate = starsUsdWithdrawRateX1000 ? starsUsdWithdrawRateX1000 / 1000 : 1;
|
||||
const configStarsPaidMessageCommissionPermille = global.appConfig?.starsPaidMessageCommissionPermille;
|
||||
const starsPaidMessageCommissionPermille = configStarsPaidMessageCommissionPermille
|
||||
? configStarsPaidMessageCommissionPermille / 1000 : 100;
|
||||
|
||||
const noPaidReactionsForUsersCount = global.settings.privacy.noPaidMessages?.allowUserIds.length || 0;
|
||||
|
||||
return {
|
||||
@ -274,9 +202,6 @@ export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||
canLimitNewMessagesWithoutPremium: global.appConfig?.canLimitNewMessagesWithoutPremium,
|
||||
canChargeForMessages: global.appConfig?.starsPaidMessagesAvailable,
|
||||
starsPaidMessageAmountMax: global.appConfig?.starsPaidMessageAmountMax || DEFAULT_MAXIMUM_CHARGE_FOR_MESSAGES,
|
||||
starsPaidMessageCommissionPermille,
|
||||
starsUsdWithdrawRate,
|
||||
noPaidReactionsForUsersCount,
|
||||
};
|
||||
})(PrivacyMessages));
|
||||
|
||||
@ -125,6 +125,7 @@
|
||||
color: var(--color-primary);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-inline-start: 2rem;
|
||||
}
|
||||
|
||||
.settings-item-simple,
|
||||
@ -283,11 +284,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.RangeSlider {
|
||||
margin-bottom: 1.0625rem;
|
||||
padding-inline: 1rem;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
.Radio:last-child {
|
||||
margin-bottom: 0;
|
||||
|
||||
@ -3,7 +3,11 @@ import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiChat, ApiMessage, ApiPeer } from '../../../api/types';
|
||||
|
||||
import { GENERAL_TOPIC_ID, SERVICE_NOTIFICATIONS_USER_ID, TME_LINK_PREFIX } from '../../../config';
|
||||
import {
|
||||
GENERAL_TOPIC_ID,
|
||||
SERVICE_NOTIFICATIONS_USER_ID,
|
||||
TME_LINK_PREFIX,
|
||||
} from '../../../config';
|
||||
import {
|
||||
getMessageInvoice, getMessageText, isChatChannel,
|
||||
} from '../../../global/helpers';
|
||||
|
||||
@ -96,7 +96,7 @@
|
||||
box-shadow: 0 1px 2px var(--color-default-shadow);
|
||||
|
||||
.RangeSlider {
|
||||
margin-bottom: 0;
|
||||
margin: 0;
|
||||
input[type="range"] {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@ -1,25 +1,38 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useCallback, useMemo, useState,
|
||||
memo, useCallback, useEffect,
|
||||
useMemo, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiChat, ApiChatBannedRights, ApiChatMember } from '../../../api/types';
|
||||
import { ManagementScreens } from '../../../types';
|
||||
import { ManagementProgress, ManagementScreens } from '../../../types';
|
||||
|
||||
import { selectChat, selectChatFullInfo } from '../../../global/selectors';
|
||||
import {
|
||||
DEFAULT_CHARGE_FOR_MESSAGES,
|
||||
} from '../../../config';
|
||||
import {
|
||||
selectChat,
|
||||
selectChatFullInfo,
|
||||
selectTabState,
|
||||
} from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
import useManagePermissions from '../hooks/useManagePermissions';
|
||||
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import PaidMessagePrice from '../../common/paidMessage/PaidMessagePrice';
|
||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
import PermissionCheckboxList from '../../main/PermissionCheckboxList';
|
||||
import FloatingActionButton from '../../ui/FloatingActionButton';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Spinner from '../../ui/Spinner';
|
||||
import Switcher from '../../ui/Switcher';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: string;
|
||||
@ -31,9 +44,13 @@ type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
progress?: ManagementProgress;
|
||||
currentUserId?: string;
|
||||
removedUsersCount: number;
|
||||
members?: ApiChatMember[];
|
||||
arePaidMessagesAvailable?: boolean;
|
||||
groupPeersPaidStars: number;
|
||||
canChargeForMessages?: boolean;
|
||||
};
|
||||
|
||||
const ITEM_HEIGHT = 48;
|
||||
@ -83,18 +100,23 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
|
||||
onScreenSelect,
|
||||
onChatMemberSelect,
|
||||
chat,
|
||||
progress,
|
||||
currentUserId,
|
||||
removedUsersCount,
|
||||
members,
|
||||
onClose,
|
||||
isActive,
|
||||
arePaidMessagesAvailable,
|
||||
canChargeForMessages,
|
||||
groupPeersPaidStars,
|
||||
}) => {
|
||||
const { updateChatDefaultBannedRights } = getActions();
|
||||
const { updateChatDefaultBannedRights, updatePaidMessagesPrice } = getActions();
|
||||
|
||||
const {
|
||||
permissions, havePermissionChanged, isLoading, handlePermissionChange, setIsLoading,
|
||||
} = useManagePermissions(chat?.defaultBannedRights);
|
||||
const lang = useOldLang();
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack({
|
||||
isActive,
|
||||
@ -116,14 +138,41 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const [isMediaDropdownOpen, setIsMediaDropdownOpen] = useState(false);
|
||||
|
||||
const handleSavePermissions = useCallback(() => {
|
||||
const [isPriceForMessagesChanged, markPriceForMessagesChanged, unmarkPriceForMessagesChanged] = useFlag();
|
||||
const [isPriceForMessagesOpen, setIsPriceForMessagesOpen] = useState(canChargeForMessages);
|
||||
const [chargeForMessages, setChargeForMessages] = useState<number>(groupPeersPaidStars);
|
||||
|
||||
useEffect(() => {
|
||||
if (progress === ManagementProgress.Complete) {
|
||||
unmarkPriceForMessagesChanged();
|
||||
}
|
||||
}, [progress]);
|
||||
|
||||
const handleSavePermissions = useLastCallback(() => {
|
||||
if (!chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
updateChatDefaultBannedRights({ chatId: chat.id, bannedRights: permissions });
|
||||
}, [chat, permissions, setIsLoading, updateChatDefaultBannedRights]);
|
||||
});
|
||||
|
||||
const handleUpdatePaidMessagesPrice = useLastCallback(() => {
|
||||
if (!chat) return;
|
||||
updatePaidMessagesPrice({
|
||||
chatId: chat?.id,
|
||||
paidMessagesStars: isPriceForMessagesOpen ? chargeForMessages : 0,
|
||||
});
|
||||
});
|
||||
|
||||
const handleUpdatePermissions = useLastCallback(() => {
|
||||
if (isPriceForMessagesChanged) {
|
||||
handleUpdatePaidMessagesPrice();
|
||||
}
|
||||
if (havePermissionChanged) {
|
||||
handleSavePermissions();
|
||||
}
|
||||
});
|
||||
|
||||
const exceptionMembers = useMemo(() => {
|
||||
if (!members) {
|
||||
@ -157,11 +206,24 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
|
||||
return result;
|
||||
}
|
||||
|
||||
const translatedString = lang(langKey);
|
||||
const translatedString = oldLang(langKey);
|
||||
|
||||
return `${result}${!result.length ? translatedString : `, ${translatedString}`}`;
|
||||
}, '');
|
||||
}, [chat, lang]);
|
||||
}, [chat, oldLang]);
|
||||
|
||||
const handleChargeStarsForMessages = useLastCallback(() => {
|
||||
setIsPriceForMessagesOpen(!isPriceForMessagesOpen);
|
||||
markPriceForMessagesChanged();
|
||||
});
|
||||
|
||||
const handleChargeForMessagesChange = useLastCallback((value: number) => {
|
||||
setChargeForMessages(value);
|
||||
markPriceForMessagesChanged();
|
||||
});
|
||||
|
||||
const arePermissionsChanged = isPriceForMessagesChanged || havePermissionChanged;
|
||||
const arePermissionsLoading = progress === ManagementProgress.InProgress || isLoading;
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -187,6 +249,43 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{arePaidMessagesAvailable && (
|
||||
<div
|
||||
className={buildClassName(
|
||||
'section',
|
||||
isMediaDropdownOpen && 'shifted',
|
||||
)}
|
||||
>
|
||||
<ListItem onClick={handleChargeStarsForMessages}>
|
||||
<span>{lang('GroupMessagesChargePrice')}</span>
|
||||
<Switcher
|
||||
id="charge_for_messages"
|
||||
label={lang('GroupMessagesChargePrice')}
|
||||
checked={isPriceForMessagesOpen}
|
||||
/>
|
||||
</ListItem>
|
||||
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
{lang('RightsChargeStarsAbout')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isPriceForMessagesOpen && (
|
||||
<div
|
||||
className={buildClassName(
|
||||
'section',
|
||||
isMediaDropdownOpen && 'shifted',
|
||||
)}
|
||||
>
|
||||
<PaidMessagePrice
|
||||
canChangeChargeForMessages
|
||||
isGroupChat
|
||||
chargeForMessages={chargeForMessages}
|
||||
onChange={handleChargeForMessagesChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={buildClassName(
|
||||
'section',
|
||||
@ -237,12 +336,12 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
|
||||
</div>
|
||||
|
||||
<FloatingActionButton
|
||||
isShown={havePermissionChanged}
|
||||
onClick={handleSavePermissions}
|
||||
isShown={arePermissionsChanged}
|
||||
onClick={handleUpdatePermissions}
|
||||
ariaLabel={lang('Save')}
|
||||
disabled={isLoading}
|
||||
disabled={arePermissionsLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
{arePermissionsLoading ? (
|
||||
<Spinner color="white" />
|
||||
) : (
|
||||
<Icon name="check" />
|
||||
@ -256,12 +355,20 @@ export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId }): StateProps => {
|
||||
const chat = selectChat(global, chatId);
|
||||
const fullInfo = selectChatFullInfo(global, chatId);
|
||||
const { progress } = selectTabState(global).management;
|
||||
|
||||
const paidMessagesStars = chat?.paidMessagesStars;
|
||||
const configStarsPaidMessageCommissionPermille = global.appConfig?.starsPaidMessageCommissionPermille;
|
||||
|
||||
return {
|
||||
chat,
|
||||
progress,
|
||||
currentUserId: global.currentUserId,
|
||||
removedUsersCount: fullInfo?.kickedMembers?.length || 0,
|
||||
members: fullInfo?.members,
|
||||
arePaidMessagesAvailable: Boolean(fullInfo?.arePaidMessagesAvailable && configStarsPaidMessageCommissionPermille),
|
||||
canChargeForMessages: Boolean(paidMessagesStars && configStarsPaidMessageCommissionPermille),
|
||||
groupPeersPaidStars: paidMessagesStars || DEFAULT_CHARGE_FOR_MESSAGES,
|
||||
};
|
||||
},
|
||||
)(ManageGroupPermissions));
|
||||
|
||||
@ -126,9 +126,7 @@
|
||||
}
|
||||
|
||||
.RangeSlider {
|
||||
margin-top: 2rem;
|
||||
margin-inline-start: 1rem;
|
||||
margin-inline-end: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.button-position {
|
||||
|
||||
@ -18,7 +18,9 @@
|
||||
.RangeSlider {
|
||||
--slider-color: var(--color-primary);
|
||||
|
||||
margin-bottom: 1rem;
|
||||
margin: 0.5rem 0 0;
|
||||
margin-inline-start: 1rem;
|
||||
margin-inline-end: 1rem;
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
|
||||
@ -2896,6 +2896,27 @@ addActionHandler('toggleChannelRecommendations', (global, actions, payload): Act
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('updatePaidMessagesPrice', async (global, actions, payload): Promise<void> => {
|
||||
const { chatId, paidMessagesStars, tabId = getCurrentTabId() } = payload;
|
||||
const chat = chatId ? selectChat(global, chatId) : undefined;
|
||||
if (!chat) return;
|
||||
|
||||
global = updateManagementProgress(global, ManagementProgress.InProgress, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
const result = await callApi('updatePaidMessagesPrice', {
|
||||
chat,
|
||||
paidMessagesStars,
|
||||
});
|
||||
|
||||
if (!result) return;
|
||||
|
||||
global = getGlobal();
|
||||
global = updateManagementProgress(global, ManagementProgress.Complete, tabId);
|
||||
global = updateChat(global, chatId, { paidMessagesStars });
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('resolveBusinessChatLink', async (global, actions, payload): Promise<void> => {
|
||||
const { slug, tabId = getCurrentTabId() } = payload;
|
||||
const result = await callApi('resolveBusinessChatLink', { slug });
|
||||
|
||||
@ -1058,6 +1058,10 @@ export interface ActionPayloads {
|
||||
chatId: string;
|
||||
isEnabled: boolean;
|
||||
};
|
||||
updatePaidMessagesPrice: {
|
||||
chatId: string;
|
||||
paidMessagesStars: number;
|
||||
} & WithTabId;
|
||||
|
||||
updateChat: {
|
||||
chatId: string;
|
||||
|
||||
@ -1709,6 +1709,7 @@ channels.toggleParticipantsHidden#6a6e7854 channel:InputChannel enabled:Bool = U
|
||||
channels.toggleViewForumAsMessages#9738bb15 channel:InputChannel enabled:Bool = Updates;
|
||||
channels.getChannelRecommendations#25a71742 flags:# channel:flags.0?InputChannel = messages.Chats;
|
||||
channels.searchPosts#d19f987b hashtag:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
||||
channels.updatePaidMessagesPrice#fc84653f channel:InputChannel send_paid_messages_stars:long = Updates;
|
||||
bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:flags.3?string about:flags.0?string description:flags.1?string = Bool;
|
||||
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
||||
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
||||
|
||||
@ -277,6 +277,7 @@
|
||||
"channels.getChannelRecommendations",
|
||||
"channels.searchPosts",
|
||||
"channels.reportSpam",
|
||||
"channels.updatePaidMessagesPrice",
|
||||
"bots.getBotRecommendations",
|
||||
"bots.canSendMessage",
|
||||
"bots.allowSendMessage",
|
||||
|
||||
6
src/types/language.d.ts
vendored
6
src/types/language.d.ts
vendored
@ -1475,6 +1475,8 @@ export interface LangPair {
|
||||
'DescriptionRestrictedMedia': undefined;
|
||||
'DescriptionScheduledPaidMediaNotAllowed': undefined;
|
||||
'DescriptionScheduledPaidMessagesNotAllowed': undefined;
|
||||
'GroupMessagesChargePrice': undefined;
|
||||
'RightsChargeStarsAbout': undefined;
|
||||
'UnlockButtonTitle': undefined;
|
||||
'PrivacySubscribeToTelegramPremium': undefined;
|
||||
'PrivacyDisableLimitedEditionStarGifts': undefined;
|
||||
@ -2387,6 +2389,10 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||
'PaidMessageTransactionDescription': {
|
||||
'percent': V;
|
||||
};
|
||||
'SetPriceGroupDescription': {
|
||||
'percent': V;
|
||||
'amount': V;
|
||||
};
|
||||
'FrozenAccountAppealSubtitle': {
|
||||
'botLink': V;
|
||||
'date': V;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user