Giveaway: Creating giveaway in groups (#4442)

Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
This commit is contained in:
Alexander Zinchuk 2024-04-19 13:37:54 +04:00
parent 97d3d31f10
commit db1d7ceea0
5 changed files with 82 additions and 58 deletions

View File

@ -42,5 +42,5 @@
} }
.picker { .picker {
height: 70vh; height: 75vh;
} }

View File

@ -1,8 +1,6 @@
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
import React, { import React, {
memo, memo, useMemo, useState,
useMemo,
useState,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../global'; import { getActions, getGlobal, withGlobal } from '../../global';
@ -10,7 +8,7 @@ import type { ApiChat, ApiChatMember, ApiUserStatus } from '../../api/types';
import { import {
filterChatsByName, filterChatsByName,
filterUsersByName, isChatChannel, isChatPublic, isUserBot, sortUserIds, filterUsersByName, isChatChannel, isChatPublic, isChatSuperGroup, isUserBot, sortUserIds,
} from '../../global/helpers'; } from '../../global/helpers';
import { selectChat, selectChatFullInfo } from '../../global/selectors'; import { selectChat, selectChatFullInfo } from '../../global/selectors';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
@ -45,6 +43,7 @@ interface StateProps {
userStatusesById: Record<string, ApiUserStatus>; userStatusesById: Record<string, ApiUserStatus>;
channelList?: (ApiChat | undefined)[] | undefined; channelList?: (ApiChat | undefined)[] | undefined;
isChannel?: boolean; isChannel?: boolean;
isSuperGroup?: boolean;
currentUserId?: string | undefined; currentUserId?: string | undefined;
} }
@ -57,6 +56,7 @@ const AppendEntityPickerModal: FC<OwnProps & StateProps> = ({
userStatusesById, userStatusesById,
entityType, entityType,
isChannel, isChannel,
isSuperGroup,
onSubmit, onSubmit,
currentUserId, currentUserId,
selectionLimit, selectionLimit,
@ -76,7 +76,8 @@ const AppendEntityPickerModal: FC<OwnProps & StateProps> = ({
const activeChatIds = getGlobal().chats.listIds.active; const activeChatIds = getGlobal().chats.listIds.active;
return activeChatIds!.map((id) => chatsById[id]) return activeChatIds!.map((id) => chatsById[id])
.filter((chat) => chat && isChatChannel(chat) && chat.id !== chatId) .filter((chat) => chat && (isChatChannel(chat)
|| isChatSuperGroup(chat)) && chat.id !== chatId)
.map((chat) => chat!.id); .map((chat) => chat!.id);
}, [chatId]); }, [chatId]);
@ -123,11 +124,11 @@ const AppendEntityPickerModal: FC<OwnProps & StateProps> = ({
return true; return true;
} }
return isChannel; return isChannel || isSuperGroup;
}), }),
false, false,
selectedChannelIds); selectedChannelIds);
}, [channelsIds, lang, searchQuery, selectedChannelIds, isChannel]); }, [channelsIds, lang, searchQuery, selectedChannelIds, isSuperGroup, isChannel]);
const handleCloseButtonClick = useLastCallback(() => { const handleCloseButtonClick = useLastCallback(() => {
onSubmit([]); onSubmit([]);
@ -248,6 +249,7 @@ const AppendEntityPickerModal: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>((global, { chatId, entityType }): StateProps => { export default memo(withGlobal<OwnProps>((global, { chatId, entityType }): StateProps => {
const { statusesById: userStatusesById } = global.users; const { statusesById: userStatusesById } = global.users;
let isChannel; let isChannel;
let isSuperGroup;
let members: ApiChatMember[] | undefined; let members: ApiChatMember[] | undefined;
let adminMembersById: Record<string, ApiChatMember> | undefined; let adminMembersById: Record<string, ApiChatMember> | undefined;
let currentUserId: string | undefined; let currentUserId: string | undefined;
@ -263,6 +265,7 @@ export default memo(withGlobal<OwnProps>((global, { chatId, entityType }): State
const chat = chatId ? selectChat(global, chatId) : undefined; const chat = chatId ? selectChat(global, chatId) : undefined;
if (chat) { if (chat) {
isChannel = isChatChannel(chat); isChannel = isChatChannel(chat);
isSuperGroup = isChatSuperGroup(chat);
} }
} }
@ -272,6 +275,7 @@ export default memo(withGlobal<OwnProps>((global, { chatId, entityType }): State
adminMembersById, adminMembersById,
userStatusesById, userStatusesById,
isChannel, isChannel,
isSuperGroup,
currentUserId, currentUserId,
}; };
})(AppendEntityPickerModal)); })(AppendEntityPickerModal));

View File

@ -15,8 +15,9 @@ import {
GIVEAWAY_MAX_ADDITIONAL_COUNTRIES, GIVEAWAY_MAX_ADDITIONAL_COUNTRIES,
GIVEAWAY_MAX_ADDITIONAL_USERS, GIVEAWAY_MAX_ADDITIONAL_USERS,
} from '../../../config'; } from '../../../config';
import { getUserFullName } from '../../../global/helpers'; import { getUserFullName, isChatChannel } from '../../../global/helpers';
import { import {
selectChat,
selectTabState, selectTabState,
} from '../../../global/selectors'; } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
@ -69,6 +70,7 @@ type StateProps = {
countryList: ApiCountry[]; countryList: ApiCountry[];
prepaidGiveaway?: ApiPrepaidGiveaway; prepaidGiveaway?: ApiPrepaidGiveaway;
countrySelectionLimit: number | undefined; countrySelectionLimit: number | undefined;
isChannel?: boolean;
}; };
type GiveawayAction = 'createRandomlyUsers' | 'createSpecificUsers'; type GiveawayAction = 'createRandomlyUsers' | 'createSpecificUsers';
@ -99,6 +101,7 @@ const GiveawayModal: FC<OwnProps & StateProps> = ({
chatId, chatId,
gifts, gifts,
isOpen, isOpen,
isChannel,
selectedMemberList, selectedMemberList,
selectedChannelList, selectedChannelList,
giveawayBoostPerPremiumLimit = GIVEAWAY_BOOST_PER_PREMIUM, giveawayBoostPerPremiumLimit = GIVEAWAY_BOOST_PER_PREMIUM,
@ -160,19 +163,19 @@ const GiveawayModal: FC<OwnProps & StateProps> = ({
const SUBSCRIBER_OPTIONS = useMemo(() => [ const SUBSCRIBER_OPTIONS = useMemo(() => [
{ {
value: 'all', value: 'all',
label: lang('BoostingAllSubscribers'), label: lang(isChannel ? 'BoostingAllSubscribers' : 'BoostingAllMembers'),
subLabel: selectedCountriesIds && selectedCountriesIds.length > 0 subLabel: selectedCountriesIds && selectedCountriesIds.length > 0
? lang('Giveaway.ReceiverType.Countries', selectedCountriesIds.length) ? lang('Giveaway.ReceiverType.Countries', selectedCountriesIds.length)
: lang('BoostingFromAllCountries'), : lang('BoostingFromAllCountries'),
}, },
{ {
value: 'new', value: 'new',
label: lang('BoostingNewSubscribers'), label: lang(isChannel ? 'BoostingNewSubscribers' : 'BoostingNewMembers'),
subLabel: selectedCountriesIds && selectedCountriesIds.length > 0 subLabel: selectedCountriesIds && selectedCountriesIds.length > 0
? lang('Giveaway.ReceiverType.Countries', selectedCountriesIds.length) ? lang('Giveaway.ReceiverType.Countries', selectedCountriesIds.length)
: lang('BoostingFromAllCountries'), : lang('BoostingFromAllCountries'),
}, },
], [lang, selectedCountriesIds]); ], [isChannel, lang, selectedCountriesIds]);
const monthQuantity = lang('Months', selectedMonthOption); const monthQuantity = lang('Months', selectedMonthOption);
@ -448,7 +451,7 @@ const GiveawayModal: FC<OwnProps & StateProps> = ({
{renderText(lang('BoostingBoostsViaGifts'))} {renderText(lang('BoostingBoostsViaGifts'))}
</h2> </h2>
<div className={styles.description}> <div className={styles.description}>
{renderText(lang('BoostingGetMoreBoost'))} {renderText(lang(isChannel ? 'BoostingGetMoreBoost' : 'BoostingGetMoreBoostsGroup'))}
</div> </div>
<div className={buildClassName(styles.header, isHeaderHidden && styles.hiddenHeader)}> <div className={buildClassName(styles.header, isHeaderHidden && styles.hiddenHeader)}>
<h2 className={styles.premiumHeaderText}> <h2 className={styles.premiumHeaderText}>
@ -522,7 +525,8 @@ const GiveawayModal: FC<OwnProps & StateProps> = ({
> >
<GroupChatInfo <GroupChatInfo
chatId={chatId!} chatId={chatId!}
status={lang('BoostingChannelWillReceiveBoost', boostQuantity, 'i')} status={lang(isChannel ? 'BoostingChannelWillReceiveBoost'
: 'BoostingGroupWillReceiveBoost', boostQuantity, 'i')}
/> />
</ListItem> </ListItem>
@ -551,7 +555,7 @@ const GiveawayModal: FC<OwnProps & StateProps> = ({
className={styles.addButton} className={styles.addButton}
iconClassName={styles.addChannel} iconClassName={styles.addChannel}
> >
{lang('BoostingAddChannel')} {lang('BoostingAddChannelOrGroup')}
</ListItem> </ListItem>
)} )}
</div> </div>
@ -565,7 +569,7 @@ const GiveawayModal: FC<OwnProps & StateProps> = ({
</div> </div>
<div className={styles.subscription}> <div className={styles.subscription}>
{renderText(lang('BoostGift.LimitSubscribersInfo'))} {renderText(lang(isChannel ? 'BoostGift.LimitSubscribersInfo' : 'lng_giveaway_users_about_group'))}
</div> </div>
<div className={styles.section}> <div className={styles.section}>
@ -726,9 +730,12 @@ export default memo(withGlobal<OwnProps>((global): StateProps => {
const { const {
giveawayModal, giveawayModal,
} = selectTabState(global); } = selectTabState(global);
const chatId = giveawayModal?.chatId;
const chat = chatId ? selectChat(global, chatId) : undefined;
const isChannel = chat && isChatChannel(chat);
return { return {
chatId: giveawayModal?.chatId, chatId,
gifts: giveawayModal?.gifts, gifts: giveawayModal?.gifts,
selectedMemberList: giveawayModal?.selectedMemberIds, selectedMemberList: giveawayModal?.selectedMemberIds,
selectedChannelList: giveawayModal?.selectedChannelIds, selectedChannelList: giveawayModal?.selectedChannelIds,
@ -737,5 +744,6 @@ export default memo(withGlobal<OwnProps>((global): StateProps => {
countrySelectionLimit: global.appConfig?.giveawayCountriesMax, countrySelectionLimit: global.appConfig?.giveawayCountriesMax,
countryList: global.countryList.general, countryList: global.countryList.general,
prepaidGiveaway: giveawayModal?.prepaidGiveaway, prepaidGiveaway: giveawayModal?.prepaidGiveaway,
isChannel,
}; };
})(GiveawayModal)); })(GiveawayModal));

View File

@ -500,7 +500,8 @@ 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 canViewBoosts = isMainThread
&& (isSuperGroup || isChannel) && (canViewStatistics || getHasAdminRight(chat, 'postStories'));
const canShowBoostModal = !canViewBoosts && (isSuperGroup || isChannel); const canShowBoostModal = !canViewBoosts && (isSuperGroup || isChannel);
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);

View File

@ -5,7 +5,8 @@ import type { ApiBoostStatistics, ApiPrepaidGiveaway } from '../../../api/types'
import type { TabState } from '../../../global/types'; import type { TabState } from '../../../global/types';
import { GIVEAWAY_BOOST_PER_PREMIUM } from '../../../config'; import { GIVEAWAY_BOOST_PER_PREMIUM } from '../../../config';
import { selectIsGiveawayGiftsPurchaseAvailable, selectTabState } from '../../../global/selectors'; import { isChatChannel } from '../../../global/helpers';
import { selectChat, selectIsGiveawayGiftsPurchaseAvailable, selectTabState } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { formatDateAtTime } from '../../../util/date/dateFormat'; import { formatDateAtTime } from '../../../util/date/dateFormat';
import { getBoostProgressInfo } from '../../common/helpers/boostInfo'; import { getBoostProgressInfo } from '../../common/helpers/boostInfo';
@ -33,6 +34,7 @@ type StateProps = {
isGiveawayAvailable?: boolean; isGiveawayAvailable?: boolean;
chatId: string; chatId: string;
giveawayBoostsPerPremium?: number; giveawayBoostsPerPremium?: number;
isChannel?: boolean;
}; };
const GIVEAWAY_IMG_LIST: { [key: number]: string } = { const GIVEAWAY_IMG_LIST: { [key: number]: string } = {
@ -46,6 +48,7 @@ const BoostStatistics = ({
isGiveawayAvailable, isGiveawayAvailable,
chatId, chatId,
giveawayBoostsPerPremium, giveawayBoostsPerPremium,
isChannel,
}: StateProps) => { }: StateProps) => {
const { const {
openChat, loadMoreBoosters, closeBoostStatistics, openGiveawayModal, openChat, loadMoreBoosters, closeBoostStatistics, openGiveawayModal,
@ -162,51 +165,56 @@ const BoostStatistics = ({
<p className="text-muted hint" key="links-hint">{lang('BoostingSelectPaidGiveaway')}</p> <p className="text-muted hint" key="links-hint">{lang('BoostingSelectPaidGiveaway')}</p>
</div> </div>
)} )}
<div className={styles.section}> {isChannel && (
<h4 className={styles.sectionHeader} dir={lang.isRtl ? 'rtl' : undefined}> <div className={styles.section}>
{lang('Boosters')} <h4 className={styles.sectionHeader} dir={lang.isRtl ? 'rtl' : undefined}>
</h4> {lang('Boosters')}
{!boostStatistics.boosterIds?.length && ( </h4>
<div className={styles.noResults}>{lang('NoBoostersHint')}</div> {!boostStatistics.boosterIds?.length && (
)} <div className={styles.noResults}>{lang('NoBoostersHint')}</div>
{boostStatistics.boosterIds?.map((userId) => ( )}
<ListItem {boostStatistics.boosterIds?.map((userId) => (
key={userId} <ListItem
className="chat-item-clickable" key={userId}
// eslint-disable-next-line react/jsx-no-bind className="chat-item-clickable"
onClick={() => handleBoosterClick(userId)} // eslint-disable-next-line react/jsx-no-bind
> onClick={() => handleBoosterClick(userId)}
<PrivateChatInfo >
className={styles.user} <PrivateChatInfo
forceShowSelf className={styles.user}
userId={userId} forceShowSelf
status={lang('BoostExpireOn', formatDateAtTime(lang, boostStatistics.boosters![userId] * 1000))} userId={userId}
/> status={lang('BoostExpireOn', formatDateAtTime(lang, boostStatistics.boosters![userId] * 1000))}
</ListItem> />
))} </ListItem>
{Boolean(boostersToLoadCount) && ( ))}
<ListItem {Boolean(boostersToLoadCount) && (
key="load-more" <ListItem
className={styles.showMore} key="load-more"
disabled={boostStatistics?.isLoadingBoosters} className={styles.showMore}
onClick={handleLoadMore} disabled={boostStatistics?.isLoadingBoosters}
> onClick={handleLoadMore}
{boostStatistics?.isLoadingBoosters ? ( >
<Spinner className={styles.loadMoreSpinner} /> {boostStatistics?.isLoadingBoosters ? (
) : ( <Spinner className={styles.loadMoreSpinner} />
<Icon name="down" className={styles.down} /> ) : (
)} <Icon name="down" className={styles.down} />
{lang('ShowVotes', boostersToLoadCount)} )}
</ListItem> {lang('ShowVotes', boostersToLoadCount)}
)} </ListItem>
</div> )}
</div>
)}
<LinkField className={styles.section} link={status!.boostUrl} withShare title={lang('LinkForBoosting')} /> <LinkField className={styles.section} link={status!.boostUrl} withShare title={lang('LinkForBoosting')} />
{isGiveawayAvailable && ( {isGiveawayAvailable && (
<div className={styles.section}> <div className={styles.section}>
<ListItem icon="gift" ripple onClick={handleGiveawayClick}> <ListItem icon="gift" ripple onClick={handleGiveawayClick}>
{lang('BoostingGetBoostsViaGifts')} {lang('BoostingGetBoostsViaGifts')}
</ListItem> </ListItem>
<p className="text-muted hint" key="links-hint">{lang('BoostingGetMoreBoosts')}</p> <p className="text-muted hint" key="links-hint">{lang(
isChannel ? 'BoostingGetMoreBoosts' : 'BoostingGetMoreBoostsGroup',
)}
</p>
</div> </div>
)} )}
</> </>
@ -221,6 +229,8 @@ export default memo(withGlobal(
const boostStatistics = tabState.boostStatistics; const boostStatistics = tabState.boostStatistics;
const isGiveawayAvailable = selectIsGiveawayGiftsPurchaseAvailable(global); const isGiveawayAvailable = selectIsGiveawayGiftsPurchaseAvailable(global);
const chatId = boostStatistics && boostStatistics.chatId; const chatId = boostStatistics && boostStatistics.chatId;
const chat = chatId ? selectChat(global, chatId) : undefined;
const isChannel = chat && isChatChannel(chat);
const giveawayBoostsPerPremium = global.appConfig?.giveawayBoostsPerPremium; const giveawayBoostsPerPremium = global.appConfig?.giveawayBoostsPerPremium;
return { return {
@ -228,6 +238,7 @@ export default memo(withGlobal(
isGiveawayAvailable, isGiveawayAvailable,
chatId: chatId!, chatId: chatId!,
giveawayBoostsPerPremium, giveawayBoostsPerPremium,
isChannel,
}; };
}, },
)(BoostStatistics)); )(BoostStatistics));