2024-08-29 15:52:16 +02:00

366 lines
12 KiB
TypeScript

import React, {
memo, useMemo, useRef, useState,
} from '../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../global';
import type {
ApiChat, ApiGiveaway, ApiGiveawayInfo, ApiGiveawayResults, ApiMessage, ApiPeer, ApiSticker,
} from '../../../api/types';
import {
getChatTitle, getUserFullName, isOwnMessage,
} from '../../../global/helpers';
import { isApiPeerChat } from '../../../global/helpers/peers';
import {
selectCanPlayAnimatedEmojis,
selectChat,
selectForwardedSender,
selectGiftStickerForDuration,
} from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import { formatDateAtTime, formatDateTimeToString } from '../../../util/dates/dateFormat';
import { isoToEmoji } from '../../../util/emoji/emoji';
import { getServerTime } from '../../../util/serverTime';
import { callApi } from '../../../api/gramjs';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import renderText from '../../common/helpers/renderText';
import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang';
import AnimatedIconFromSticker from '../../common/AnimatedIconFromSticker';
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
import PickerSelectedItem from '../../common/pickers/PickerSelectedItem';
import Button from '../../ui/Button';
import ConfirmDialog from '../../ui/ConfirmDialog';
import Separator from '../../ui/Separator';
import styles from './Giveaway.module.scss';
type OwnProps = {
message: ApiMessage;
};
type StateProps = {
chat: ApiChat;
sender?: ApiPeer;
giftSticker?: ApiSticker;
canPlayAnimatedEmojis?: boolean;
};
const NBSP = '\u00A0';
const GIFT_STICKER_SIZE = 175;
const RESULT_STICKER_SIZE = 150;
const Giveaway = ({
chat,
sender,
message,
canPlayAnimatedEmojis,
giftSticker,
}: OwnProps & StateProps) => {
const { openChat } = getActions();
const isLoadingInfo = useRef(false);
const [giveawayInfo, setGiveawayInfo] = useState<ApiGiveawayInfo | undefined>();
const lang = useOldLang();
const { giveaway, giveawayResults } = message.content;
const isResults = Boolean(giveawayResults);
const {
months, untilDate, prizeDescription,
} = (giveaway || giveawayResults)!;
const isOwn = isOwnMessage(message);
const quantity = isResults ? giveawayResults.winnersCount : giveaway!.quantity;
const hasEnded = getServerTime() > untilDate;
const countryList = useMemo(() => {
if (isResults) return undefined;
const translatedNames = new Intl.DisplayNames([lang.code!, 'en'].filter(Boolean), { type: 'region' });
return giveaway?.countries?.map((countryCode) => (
`${isoToEmoji(countryCode)}${NBSP}${translatedNames.of(countryCode)}`
)).join(', ');
}, [giveaway, isResults, lang.code]);
const handlePeerClick = useLastCallback((channelId: string) => {
openChat({ id: channelId });
});
const handleShowInfoClick = useLastCallback(async () => {
if (isLoadingInfo.current) return;
isLoadingInfo.current = true;
const result = await callApi('fetchGiveawayInfo', {
peer: chat,
messageId: message.id,
});
setGiveawayInfo(result);
isLoadingInfo.current = false;
});
const handleCloseInfo = useLastCallback(() => {
setGiveawayInfo(undefined);
});
const giveawayInfoTitle = useMemo(() => {
if (!giveawayInfo) return undefined;
return lang(giveawayInfo.type === 'results' ? 'BoostingGiveawayEnd' : 'BoostingGiveAwayAbout');
}, [giveawayInfo, lang]);
function renderGiveawayDescription(media: ApiGiveaway) {
const channelIds = media.channelIds;
return (
<>
<div className={styles.section}>
<strong className={styles.title}>
{renderText(lang('BoostingGiveawayPrizes'), ['simple_markdown'])}
</strong>
{prizeDescription && (
<>
<p className={styles.description}>
{renderText(
lang('BoostingGiveawayMsgPrizes', [quantity, prizeDescription], undefined, quantity),
['simple_markdown'],
)}
</p>
<Separator>{lang('BoostingGiveawayMsgWithDivider')}</Separator>
</>
)}
<p className={styles.description}>
{renderText(lang('Chat.Giveaway.Info.Subscriptions', quantity), ['simple_markdown'])}
<br />
{renderText(lang(
'ActionGiftPremiumSubtitle',
lang('Chat.Giveaway.Info.Months', months),
), ['simple_markdown'])}
</p>
</div>
<div className={styles.section}>
<strong className={styles.title}>
{renderText(lang('BoostingGiveawayMsgParticipants'), ['simple_markdown'])}
</strong>
<p className={styles.description}>
{renderText(lang('BoostingGiveawayMsgAllSubsPlural', channelIds.length), ['simple_markdown'])}
</p>
<div className={styles.peers}>
{channelIds.map((peerId) => (
<PickerSelectedItem
peerId={peerId}
forceShowSelf
fluid
withPeerColors={!isOwn}
className={styles.peer}
clickArg={peerId}
onClick={handlePeerClick}
/>
))}
</div>
{countryList && (
<span>{renderText(lang('Chat.Giveaway.Message.CountriesFrom', countryList))}</span>
)}
</div>
<div className={styles.section}>
<strong className={styles.title}>
{renderText(lang('BoostingWinnersDate'), ['simple_markdown'])}
</strong>
<p className={styles.description}>
{formatDateTimeToString(untilDate * 1000, lang.code, true)}
</p>
</div>
</>
);
}
function renderGiveawayResultsDescription(media: ApiGiveawayResults) {
const winnerIds = media.winnerIds;
return (
<>
<div className={styles.section}>
<strong className={styles.title}>
{renderText(lang('BoostingGiveawayResultsMsgWinnersSelected'), ['simple_markdown'])}
</strong>
<p className={styles.description}>
{renderText(lang('BoostingGiveawayResultsMsgWinnersTitle', winnerIds.length), ['simple_markdown'])}
</p>
<strong className={styles.title}>
{lang('lng_prizes_results_winners')}
</strong>
<div className={styles.peers}>
{winnerIds.map((peerId) => (
<PickerSelectedItem
peerId={peerId}
forceShowSelf
fluid
withPeerColors={!isOwn}
className={styles.peer}
clickArg={peerId}
onClick={handlePeerClick}
/>
))}
</div>
</div>
<div className={styles.section}>
<p className={styles.description}>
{lang('BoostingGiveawayResultsMsgAllWinnersReceivedLinks')}
</p>
</div>
</>
);
}
function renderGiveawayInfo() {
if (!sender || !giveawayInfo) return undefined;
const isResultsInfo = giveawayInfo.type === 'results';
const chatTitle = isApiPeerChat(sender) ? getChatTitle(lang, sender) : getUserFullName(sender);
const duration = lang('Chat.Giveaway.Info.Months', months);
const endDate = formatDateAtTime(lang, untilDate * 1000);
const otherChannelsCount = giveaway?.channelIds ? giveaway.channelIds.length - 1 : 0;
const otherChannelsString = lang('Chat.Giveaway.Info.OtherChannels', otherChannelsCount);
const isSeveral = otherChannelsCount > 0;
const firstKey = isResultsInfo ? 'BoostingGiveawayHowItWorksTextEnd' : 'BoostingGiveawayHowItWorksText';
const firstParagraph = lang(firstKey, [chatTitle, quantity, duration], undefined, quantity);
const additionalPrizes = prizeDescription
? lang('BoostingGiveawayHowItWorksIncludeText', [chatTitle, quantity, prizeDescription], undefined, quantity)
: undefined;
let secondKey = '';
if (isResultsInfo) {
secondKey = isSeveral ? 'BoostingGiveawayHowItWorksSubTextSeveralEnd' : 'BoostingGiveawayHowItWorksSubTextEnd';
} else {
secondKey = isSeveral ? 'BoostingGiveawayHowItWorksSubTextSeveral' : 'BoostingGiveawayHowItWorksSubText';
}
let secondParagraph = lang(secondKey, [endDate, quantity, chatTitle, otherChannelsCount], undefined, quantity);
if (isResultsInfo && giveawayInfo.activatedCount) {
secondParagraph += ` ${lang('BoostingGiveawayUsedLinksPlural', giveawayInfo.activatedCount)}`;
}
let result = '';
if (isResultsInfo) {
if (giveawayInfo.isRefunded) {
result = lang('BoostingGiveawayCanceledByPayment');
} else {
result = lang(giveawayInfo.isWinner ? 'BoostingGiveawayYouWon' : 'BoostingGiveawayYouNotWon');
}
}
let lastParagraph = '';
if (isResultsInfo) {
// Nothing
} else if (giveawayInfo.disallowedCountry) {
lastParagraph = lang('BoostingGiveawayNotEligibleCountry');
} else if (giveawayInfo.adminDisallowedChatId) {
// Since rerenders are not expected, we can use the global state directly
const chatsById = getGlobal().chats.byId;
const disallowedChat = chatsById[giveawayInfo.adminDisallowedChatId];
const disallowedChatTitle = disallowedChat && getChatTitle(lang, disallowedChat);
lastParagraph = lang('BoostingGiveawayNotEligibleAdmin', disallowedChatTitle);
} else if (giveawayInfo.joinedTooEarlyDate) {
const joinedTooEarlyDate = formatDateAtTime(lang, giveawayInfo.joinedTooEarlyDate * 1000);
lastParagraph = lang('BoostingGiveawayNotEligible', joinedTooEarlyDate);
} else if (giveawayInfo.isParticipating) {
lastParagraph = isSeveral
? lang('Chat.Giveaway.Info.ParticipatingMany', [chatTitle, otherChannelsCount])
: lang('Chat.Giveaway.Info.Participating', chatTitle);
} else {
lastParagraph = isSeveral
? lang('Chat.Giveaway.Info.NotQualifiedMany', [chatTitle, otherChannelsString, endDate])
: lang('Chat.Giveaway.Info.NotQualified', [chatTitle, endDate]);
}
return (
<>
{result && (
<p className={styles.result}>
{renderText(result, ['simple_markdown'])}
</p>
)}
<p>
{renderText(firstParagraph, ['simple_markdown'])}
</p>
{additionalPrizes && (
<p>
{renderText(additionalPrizes, ['simple_markdown'])}
</p>
)}
<p>
{renderText(secondParagraph, ['simple_markdown'])}
</p>
{lastParagraph && (
<p>
{renderText(lastParagraph, ['simple_markdown'])}
</p>
)}
</>
);
}
return (
<div className={styles.root}>
<div className={buildClassName(styles.sticker, isResults && styles.resultSticker)}>
{isResults ? (
<AnimatedIconWithPreview
size={RESULT_STICKER_SIZE}
tgsUrl={LOCAL_TGS_URLS.PartyPopper}
nonInteractive
noLoop
/>
) : (
<AnimatedIconFromSticker
sticker={giftSticker}
play={canPlayAnimatedEmojis && hasEnded}
noLoop
nonInteractive
size={GIFT_STICKER_SIZE}
/>
)}
<span className={styles.count}>
{`x${quantity}`}
</span>
</div>
{isResults ? renderGiveawayResultsDescription(giveawayResults) : renderGiveawayDescription(giveaway!)}
<Button
className={styles.button}
color="adaptive"
size="smaller"
onClick={handleShowInfoClick}
>
{lang('BoostingHowItWork')}
</Button>
<ConfirmDialog
isOpen={Boolean(giveawayInfo)}
isOnlyConfirm
title={giveawayInfoTitle}
confirmHandler={handleCloseInfo}
onClose={handleCloseInfo}
>
{renderGiveawayInfo()}
</ConfirmDialog>
</div>
);
};
export default memo(withGlobal<OwnProps>(
(global, { message }): StateProps => {
const { giveaway } = message.content;
const chat = selectChat(global, message.chatId)!;
const sender = selectChat(global, giveaway?.channelIds[0]!)
|| selectForwardedSender(global, message) || chat;
const sticker = giveaway && selectGiftStickerForDuration(global, giveaway.months);
return {
chat,
sender,
giftSticker: sticker,
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
};
},
)(Giveaway));