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 {
height: 70vh;
height: 75vh;
}

View File

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

View File

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

View File

@ -500,7 +500,8 @@ export default memo(withGlobal<OwnProps>(
const canCreateVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && !chat.isCallActive
&& (chat.adminRights?.manageCall || (chat.isCreator && isChatBasicGroup(chat)));
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 pendingJoinRequests = isMainThread ? chatFullInfo?.requestsPending : undefined;
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 { 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 { formatDateAtTime } from '../../../util/date/dateFormat';
import { getBoostProgressInfo } from '../../common/helpers/boostInfo';
@ -33,6 +34,7 @@ type StateProps = {
isGiveawayAvailable?: boolean;
chatId: string;
giveawayBoostsPerPremium?: number;
isChannel?: boolean;
};
const GIVEAWAY_IMG_LIST: { [key: number]: string } = {
@ -46,6 +48,7 @@ const BoostStatistics = ({
isGiveawayAvailable,
chatId,
giveawayBoostsPerPremium,
isChannel,
}: StateProps) => {
const {
openChat, loadMoreBoosters, closeBoostStatistics, openGiveawayModal,
@ -162,51 +165,56 @@ const BoostStatistics = ({
<p className="text-muted hint" key="links-hint">{lang('BoostingSelectPaidGiveaway')}</p>
</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>
{isChannel && (
<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>
)}
<LinkField className={styles.section} link={status!.boostUrl} withShare title={lang('LinkForBoosting')} />
{isGiveawayAvailable && (
<div className={styles.section}>
<ListItem icon="gift" ripple onClick={handleGiveawayClick}>
{lang('BoostingGetBoostsViaGifts')}
</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>
)}
</>
@ -221,6 +229,8 @@ export default memo(withGlobal(
const boostStatistics = tabState.boostStatistics;
const isGiveawayAvailable = selectIsGiveawayGiftsPurchaseAvailable(global);
const chatId = boostStatistics && boostStatistics.chatId;
const chat = chatId ? selectChat(global, chatId) : undefined;
const isChannel = chat && isChatChannel(chat);
const giveawayBoostsPerPremium = global.appConfig?.giveawayBoostsPerPremium;
return {
@ -228,6 +238,7 @@ export default memo(withGlobal(
isGiveawayAvailable,
chatId: chatId!,
giveawayBoostsPerPremium,
isChannel,
};
},
)(BoostStatistics));