Introduce giveaway support (#3960)
This commit is contained in:
parent
1eea365bb2
commit
e050f427ee
@ -6,6 +6,7 @@ import type {
|
|||||||
ApiDocument,
|
ApiDocument,
|
||||||
ApiFormattedText,
|
ApiFormattedText,
|
||||||
ApiGame,
|
ApiGame,
|
||||||
|
ApiGiveaway,
|
||||||
ApiInvoice,
|
ApiInvoice,
|
||||||
ApiLocation,
|
ApiLocation,
|
||||||
ApiMessageExtendedMediaPreview,
|
ApiMessageExtendedMediaPreview,
|
||||||
@ -49,7 +50,7 @@ export function buildMessageContent(
|
|||||||
const hasUnsupportedMedia = mtpMessage.media instanceof GramJs.MessageMediaUnsupported;
|
const hasUnsupportedMedia = mtpMessage.media instanceof GramJs.MessageMediaUnsupported;
|
||||||
|
|
||||||
if (mtpMessage.message && !hasUnsupportedMedia
|
if (mtpMessage.message && !hasUnsupportedMedia
|
||||||
&& !content.sticker && !content.poll && !content.contact && !(content.video?.isRound)) {
|
&& !content.sticker && !content.poll && !content.contact && !content.video?.isRound) {
|
||||||
content = {
|
content = {
|
||||||
...content,
|
...content,
|
||||||
text: buildMessageTextContent(mtpMessage.message, mtpMessage.entities),
|
text: buildMessageTextContent(mtpMessage.message, mtpMessage.entities),
|
||||||
@ -118,6 +119,9 @@ export function buildMessageMediaContent(media: GramJs.TypeMessageMedia): MediaC
|
|||||||
const storyData = buildMessageStoryData(media);
|
const storyData = buildMessageStoryData(media);
|
||||||
if (storyData) return { storyData };
|
if (storyData) return { storyData };
|
||||||
|
|
||||||
|
const giveaway = buildGiweawayFromMedia(media);
|
||||||
|
if (giveaway) return { giveaway };
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,6 +470,31 @@ function buildGame(media: GramJs.MessageMediaGame): ApiGame | undefined {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildGiweawayFromMedia(media: GramJs.TypeMessageMedia): ApiGiveaway | undefined {
|
||||||
|
if (!(media instanceof GramJs.MessageMediaGiveaway)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildGiveaway(media);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGiveaway(media: GramJs.MessageMediaGiveaway): ApiGiveaway | undefined {
|
||||||
|
const {
|
||||||
|
channels, months, quantity, untilDate, countriesIso2, onlyNewSubscribers,
|
||||||
|
} = media;
|
||||||
|
|
||||||
|
const channelIds = channels.map((channel) => buildApiPeerId(channel, 'channel'));
|
||||||
|
|
||||||
|
return {
|
||||||
|
channelIds,
|
||||||
|
months,
|
||||||
|
quantity,
|
||||||
|
untilDate,
|
||||||
|
countries: countriesIso2,
|
||||||
|
isOnlyForNewSubscribers: onlyNewSubscribers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function buildMessageStoryData(media: GramJs.TypeMessageMedia): ApiMessageStoryData | undefined {
|
export function buildMessageStoryData(media: GramJs.TypeMessageMedia): ApiMessageStoryData | undefined {
|
||||||
if (!(media instanceof GramJs.MessageMediaStory)) {
|
if (!(media instanceof GramJs.MessageMediaStory)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -336,6 +336,8 @@ function buildAction(
|
|||||||
let months: number | undefined;
|
let months: number | undefined;
|
||||||
let topicEmojiIconId: string | undefined;
|
let topicEmojiIconId: string | undefined;
|
||||||
let isTopicAction: boolean | undefined;
|
let isTopicAction: boolean | undefined;
|
||||||
|
let slug: string | undefined;
|
||||||
|
let isGiveaway: boolean | undefined;
|
||||||
|
|
||||||
const targetUserIds = 'users' in action
|
const targetUserIds = 'users' in action
|
||||||
? action.users && action.users.map((id) => buildApiPeerId(id, 'user'))
|
? action.users && action.users.map((id) => buildApiPeerId(id, 'user'))
|
||||||
@ -523,6 +525,17 @@ function buildAction(
|
|||||||
translationValues.push('%target_user%');
|
translationValues.push('%target_user%');
|
||||||
|
|
||||||
if (targetPeerId) targetUserIds.push(targetPeerId);
|
if (targetPeerId) targetUserIds.push(targetPeerId);
|
||||||
|
} else if (action instanceof GramJs.MessageActionGiveawayLaunch) {
|
||||||
|
text = 'BoostingGiveawayJustStarted';
|
||||||
|
translationValues.push('%action_origin%');
|
||||||
|
} else if (action instanceof GramJs.MessageActionGiftCode) {
|
||||||
|
text = 'BoostingReceivedGiftNoName';
|
||||||
|
slug = action.slug;
|
||||||
|
months = action.months;
|
||||||
|
isGiveaway = Boolean(action.viaGiveaway);
|
||||||
|
if (action.boostPeer) {
|
||||||
|
targetChatId = getApiChatIdFromMtpPeer(action.boostPeer);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
text = 'ChatList.UnsupportedMessage';
|
text = 'ChatList.UnsupportedMessage';
|
||||||
}
|
}
|
||||||
@ -541,6 +554,8 @@ function buildAction(
|
|||||||
amount,
|
amount,
|
||||||
currency,
|
currency,
|
||||||
giftCryptoInfo,
|
giftCryptoInfo,
|
||||||
|
isGiveaway,
|
||||||
|
slug,
|
||||||
translationValues,
|
translationValues,
|
||||||
call,
|
call,
|
||||||
phoneCall,
|
phoneCall,
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import type { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiInvoice, ApiLabeledPrice, ApiPaymentCredentials,
|
ApiBoostsStatus,
|
||||||
|
ApiCheckedGiftCode,
|
||||||
|
ApiGiveawayInfo,
|
||||||
|
ApiInvoice, ApiLabeledPrice, ApiMyBoost, ApiPaymentCredentials,
|
||||||
ApiPaymentForm, ApiPaymentSavedInfo, ApiPremiumPromo, ApiPremiumSubscriptionOption,
|
ApiPaymentForm, ApiPaymentSavedInfo, ApiPremiumPromo, ApiPremiumSubscriptionOption,
|
||||||
ApiReceipt,
|
ApiReceipt,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
@ -8,6 +12,8 @@ import type {
|
|||||||
import { buildApiMessageEntity } from './common';
|
import { buildApiMessageEntity } from './common';
|
||||||
import { omitVirtualClassFields } from './helpers';
|
import { omitVirtualClassFields } from './helpers';
|
||||||
import { buildApiDocument, buildApiWebDocument } from './messageContent';
|
import { buildApiDocument, buildApiWebDocument } from './messageContent';
|
||||||
|
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
||||||
|
import { buildStatisticsPercentage } from './statistics';
|
||||||
|
|
||||||
export function buildShippingOptions(shippingOptions: GramJs.ShippingOption[] | undefined) {
|
export function buildShippingOptions(shippingOptions: GramJs.ShippingOption[] | undefined) {
|
||||||
if (!shippingOptions) {
|
if (!shippingOptions) {
|
||||||
@ -197,3 +203,92 @@ function buildApiPremiumSubscriptionOption(option: GramJs.PremiumSubscriptionOpt
|
|||||||
export function buildApiPaymentCredentials(credentials: GramJs.PaymentSavedCredentialsCard[]): ApiPaymentCredentials[] {
|
export function buildApiPaymentCredentials(credentials: GramJs.PaymentSavedCredentialsCard[]): ApiPaymentCredentials[] {
|
||||||
return credentials.map(({ id, title }) => ({ id, title }));
|
return credentials.map(({ id, title }) => ({ id, title }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildApiBoostsStatus(boostStatus: GramJs.premium.BoostsStatus): ApiBoostsStatus {
|
||||||
|
const {
|
||||||
|
level, boostUrl, boosts, myBoost, currentLevelBoosts, nextLevelBoosts, premiumAudience,
|
||||||
|
} = boostStatus;
|
||||||
|
return {
|
||||||
|
level,
|
||||||
|
currentLevelBoosts,
|
||||||
|
boosts,
|
||||||
|
hasMyBoost: Boolean(myBoost),
|
||||||
|
boostUrl,
|
||||||
|
nextLevelBoosts,
|
||||||
|
...(premiumAudience && { premiumSubscribers: buildStatisticsPercentage(premiumAudience) }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildApiMyBoost(myBoost: GramJs.MyBoost): ApiMyBoost {
|
||||||
|
const {
|
||||||
|
date, expires, slot, cooldownUntilDate, peer,
|
||||||
|
} = myBoost;
|
||||||
|
|
||||||
|
return {
|
||||||
|
date,
|
||||||
|
expires,
|
||||||
|
slot,
|
||||||
|
cooldownUntil: cooldownUntilDate,
|
||||||
|
chatId: peer && getApiChatIdFromMtpPeer(peer),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildApiGiveawayInfo(info: GramJs.payments.TypeGiveawayInfo): ApiGiveawayInfo | undefined {
|
||||||
|
if (info instanceof GramJs.payments.GiveawayInfo) {
|
||||||
|
const {
|
||||||
|
startDate,
|
||||||
|
adminDisallowedChatId,
|
||||||
|
disallowedCountry,
|
||||||
|
joinedTooEarlyDate,
|
||||||
|
participating,
|
||||||
|
preparingResults,
|
||||||
|
} = info;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'active',
|
||||||
|
startDate,
|
||||||
|
isParticipating: participating,
|
||||||
|
adminDisallowedChatId: adminDisallowedChatId?.toString(),
|
||||||
|
disallowedCountry,
|
||||||
|
joinedTooEarlyDate,
|
||||||
|
isPreparingResults: preparingResults,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const {
|
||||||
|
activatedCount,
|
||||||
|
finishDate,
|
||||||
|
giftCodeSlug,
|
||||||
|
winner,
|
||||||
|
refunded,
|
||||||
|
startDate,
|
||||||
|
winnersCount,
|
||||||
|
} = info;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'results',
|
||||||
|
startDate,
|
||||||
|
activatedCount,
|
||||||
|
finishDate,
|
||||||
|
winnersCount,
|
||||||
|
giftCodeSlug,
|
||||||
|
isRefunded: refunded,
|
||||||
|
isWinner: winner,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildApiCheckedGiftCode(giftcode: GramJs.payments.TypeCheckedGiftCode): ApiCheckedGiftCode {
|
||||||
|
const {
|
||||||
|
date, fromId, months, giveawayMsgId, toId, usedDate, viaGiveaway,
|
||||||
|
} = giftcode;
|
||||||
|
|
||||||
|
return {
|
||||||
|
date,
|
||||||
|
months,
|
||||||
|
toId: toId && buildApiPeerId(toId, 'user'),
|
||||||
|
fromId: fromId && getApiChatIdFromMtpPeer(fromId),
|
||||||
|
usedAt: usedDate,
|
||||||
|
isFromGiveaway: viaGiveaway,
|
||||||
|
giveawayMessageId: giveawayMsgId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiBoostsStatus,
|
|
||||||
ApiMediaArea,
|
ApiMediaArea,
|
||||||
ApiMediaAreaCoordinates,
|
ApiMediaAreaCoordinates,
|
||||||
ApiMyBoost,
|
|
||||||
ApiStealthMode,
|
ApiStealthMode,
|
||||||
ApiStoryView,
|
ApiStoryView,
|
||||||
ApiTypeStory,
|
ApiTypeStory,
|
||||||
@ -16,7 +14,6 @@ import { buildPrivacyRules } from './common';
|
|||||||
import { buildGeoPoint, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
|
import { buildGeoPoint, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
|
||||||
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
||||||
import { buildApiReaction, buildReactionCount } from './reactions';
|
import { buildApiReaction, buildReactionCount } from './reactions';
|
||||||
import { buildStatisticsPercentage } from './statistics';
|
|
||||||
|
|
||||||
export function buildApiStory(peerId: string, story: GramJs.TypeStoryItem): ApiTypeStory {
|
export function buildApiStory(peerId: string, story: GramJs.TypeStoryItem): ApiTypeStory {
|
||||||
if (story instanceof GramJs.StoryItemDeleted) {
|
if (story instanceof GramJs.StoryItemDeleted) {
|
||||||
@ -169,32 +166,3 @@ export function buildApiPeerStories(peerStories: GramJs.PeerStories) {
|
|||||||
|
|
||||||
return buildCollectionByCallback(peerStories.stories, (story) => [story.id, buildApiStory(peerId, story)]);
|
return buildCollectionByCallback(peerStories.stories, (story) => [story.id, buildApiStory(peerId, story)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildApiBoostsStatus(boostStatus: GramJs.premium.BoostsStatus): ApiBoostsStatus {
|
|
||||||
const {
|
|
||||||
level, boostUrl, boosts, myBoost, currentLevelBoosts, nextLevelBoosts, premiumAudience,
|
|
||||||
} = boostStatus;
|
|
||||||
return {
|
|
||||||
level,
|
|
||||||
currentLevelBoosts,
|
|
||||||
boosts,
|
|
||||||
hasMyBoost: Boolean(myBoost),
|
|
||||||
boostUrl,
|
|
||||||
nextLevelBoosts,
|
|
||||||
...(premiumAudience && { premiumSubscribers: buildStatisticsPercentage(premiumAudience) }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildApiMyBoost(myBoost: GramJs.MyBoost): ApiMyBoost {
|
|
||||||
const {
|
|
||||||
date, expires, slot, cooldownUntilDate, peer,
|
|
||||||
} = myBoost;
|
|
||||||
|
|
||||||
return {
|
|
||||||
date,
|
|
||||||
expires,
|
|
||||||
slot,
|
|
||||||
cooldownUntil: cooldownUntilDate,
|
|
||||||
chatId: peer && getApiChatIdFromMtpPeer(peer),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@ -80,10 +80,6 @@ export {
|
|||||||
allowBotSendMessages, fetchBotCanSendMessage, invokeWebViewCustomMethod,
|
allowBotSendMessages, fetchBotCanSendMessage, invokeWebViewCustomMethod,
|
||||||
} from './bots';
|
} from './bots';
|
||||||
|
|
||||||
export {
|
|
||||||
validateRequestedInfo, sendPaymentForm, getPaymentForm, getReceipt, fetchPremiumPromo, fetchTemporaryPaymentPassword,
|
|
||||||
} from './payments';
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getGroupCall, joinGroupCall, discardGroupCall, createGroupCall,
|
getGroupCall, joinGroupCall, discardGroupCall, createGroupCall,
|
||||||
editGroupCallTitle, editGroupCallParticipant, exportGroupCallInvite, fetchGroupCallParticipants,
|
editGroupCallTitle, editGroupCallParticipant, exportGroupCallInvite, fetchGroupCallParticipants,
|
||||||
@ -111,3 +107,8 @@ export {
|
|||||||
} from '../localDb';
|
} from '../localDb';
|
||||||
|
|
||||||
export * from './stories';
|
export * from './stories';
|
||||||
|
|
||||||
|
export {
|
||||||
|
validateRequestedInfo, sendPaymentForm, getPaymentForm, getReceipt, fetchPremiumPromo, fetchTemporaryPaymentPassword,
|
||||||
|
applyBoost, fetchBoostsList, fetchBoostsStatus, fetchGiveawayInfo, fetchMyBoosts, applyGiftCode, checkGiftCode,
|
||||||
|
} from './payments';
|
||||||
|
|||||||
@ -2,12 +2,18 @@ import BigInt from 'big-integer';
|
|||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChat, ApiRequestInputInvoice,
|
ApiChat, ApiPeer, ApiRequestInputInvoice,
|
||||||
OnApiUpdate,
|
OnApiUpdate,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
|
import { buildCollectionByCallback } from '../../../util/iteratees';
|
||||||
|
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||||
import {
|
import {
|
||||||
|
buildApiBoostsStatus,
|
||||||
|
buildApiCheckedGiftCode,
|
||||||
|
buildApiGiveawayInfo,
|
||||||
buildApiInvoiceFromForm,
|
buildApiInvoiceFromForm,
|
||||||
|
buildApiMyBoost,
|
||||||
buildApiPaymentForm,
|
buildApiPaymentForm,
|
||||||
buildApiPremiumPromo,
|
buildApiPremiumPromo,
|
||||||
buildApiReceipt,
|
buildApiReceipt,
|
||||||
@ -186,3 +192,145 @@ export async function fetchTemporaryPaymentPassword(password: string) {
|
|||||||
validUntil: result.validUntil,
|
validUntil: result.validUntil,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchMyBoosts() {
|
||||||
|
const result = await invokeRequest(new GramJs.premium.GetMyBoosts());
|
||||||
|
|
||||||
|
if (!result) return undefined;
|
||||||
|
|
||||||
|
addEntitiesToLocalDb(result.users);
|
||||||
|
addEntitiesToLocalDb(result.chats);
|
||||||
|
|
||||||
|
const users = result.users.map(buildApiUser).filter(Boolean);
|
||||||
|
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
||||||
|
const boosts = result.myBoosts.map(buildApiMyBoost);
|
||||||
|
|
||||||
|
return {
|
||||||
|
users,
|
||||||
|
chats,
|
||||||
|
boosts,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyBoost({
|
||||||
|
chat,
|
||||||
|
slots,
|
||||||
|
} : {
|
||||||
|
chat: ApiChat;
|
||||||
|
slots: number[];
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.premium.ApplyBoost({
|
||||||
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
|
slots,
|
||||||
|
}), {
|
||||||
|
shouldReturnTrue: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchBoostsStatus({
|
||||||
|
chat,
|
||||||
|
}: {
|
||||||
|
chat: ApiChat;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.premium.GetBoostsStatus({
|
||||||
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildApiBoostsStatus(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchBoostsList({
|
||||||
|
chat,
|
||||||
|
offset = '',
|
||||||
|
limit,
|
||||||
|
}: {
|
||||||
|
chat: ApiChat;
|
||||||
|
offset?: string;
|
||||||
|
limit?: number;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.premium.GetBoostsList({
|
||||||
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
addEntitiesToLocalDb(result.users);
|
||||||
|
|
||||||
|
const users = result.users.map(buildApiUser).filter(Boolean);
|
||||||
|
|
||||||
|
const userBoosts = result.boosts.filter((boost) => boost.userId);
|
||||||
|
const boosterIds = userBoosts.map((boost) => boost.userId!.toString());
|
||||||
|
const boosters = buildCollectionByCallback(userBoosts, (boost) => (
|
||||||
|
[boost.userId!.toString(), boost.expires]
|
||||||
|
));
|
||||||
|
|
||||||
|
return {
|
||||||
|
count: result.count,
|
||||||
|
users,
|
||||||
|
boosters,
|
||||||
|
boosterIds,
|
||||||
|
nextOffset: result.nextOffset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchGiveawayInfo({
|
||||||
|
peer,
|
||||||
|
messageId,
|
||||||
|
}: {
|
||||||
|
peer: ApiPeer;
|
||||||
|
messageId: number;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.payments.GetGiveawayInfo({
|
||||||
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
msgId: messageId,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildApiGiveawayInfo(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkGiftCode({
|
||||||
|
slug,
|
||||||
|
}: {
|
||||||
|
slug: string;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.payments.CheckGiftCode({
|
||||||
|
slug,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
addEntitiesToLocalDb(result.users);
|
||||||
|
addEntitiesToLocalDb(result.chats);
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: buildApiCheckedGiftCode(result),
|
||||||
|
users: result.users.map(buildApiUser).filter(Boolean),
|
||||||
|
chats: result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyGiftCode({
|
||||||
|
slug,
|
||||||
|
}: {
|
||||||
|
slug: string;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.payments.ApplyGiftCode({
|
||||||
|
slug,
|
||||||
|
}), {
|
||||||
|
shouldReturnTrue: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -17,8 +17,6 @@ import { buildCollectionByCallback } from '../../../util/iteratees';
|
|||||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||||
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
||||||
import {
|
import {
|
||||||
buildApiBoostsStatus,
|
|
||||||
buildApiMyBoost,
|
|
||||||
buildApiPeerStories,
|
buildApiPeerStories,
|
||||||
buildApiStealthMode,
|
buildApiStealthMode,
|
||||||
buildApiStory,
|
buildApiStory,
|
||||||
@ -428,91 +426,3 @@ export function activateStealthMode({
|
|||||||
shouldReturnTrue: true,
|
shouldReturnTrue: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchMyBoosts() {
|
|
||||||
const result = await invokeRequest(new GramJs.premium.GetMyBoosts());
|
|
||||||
|
|
||||||
if (!result) return undefined;
|
|
||||||
|
|
||||||
addEntitiesToLocalDb(result.users);
|
|
||||||
addEntitiesToLocalDb(result.chats);
|
|
||||||
|
|
||||||
const users = result.users.map(buildApiUser).filter(Boolean);
|
|
||||||
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
|
||||||
const boosts = result.myBoosts.map(buildApiMyBoost);
|
|
||||||
|
|
||||||
return {
|
|
||||||
users,
|
|
||||||
chats,
|
|
||||||
boosts,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyBoost({
|
|
||||||
chat,
|
|
||||||
slots,
|
|
||||||
} : {
|
|
||||||
chat: ApiChat;
|
|
||||||
slots: number[];
|
|
||||||
}) {
|
|
||||||
return invokeRequest(new GramJs.premium.ApplyBoost({
|
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
|
||||||
slots,
|
|
||||||
}), {
|
|
||||||
shouldReturnTrue: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchBoostsStatus({
|
|
||||||
chat,
|
|
||||||
}: {
|
|
||||||
chat: ApiChat;
|
|
||||||
}) {
|
|
||||||
const result = await invokeRequest(new GramJs.premium.GetBoostsStatus({
|
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildApiBoostsStatus(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchBoostersList({
|
|
||||||
chat,
|
|
||||||
offset = '',
|
|
||||||
limit,
|
|
||||||
}: {
|
|
||||||
chat: ApiChat;
|
|
||||||
offset?: string;
|
|
||||||
limit?: number;
|
|
||||||
}) {
|
|
||||||
const result = await invokeRequest(new GramJs.premium.GetBoostsList({
|
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
|
||||||
offset,
|
|
||||||
limit,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
addEntitiesToLocalDb(result.users);
|
|
||||||
|
|
||||||
const users = result.users.map(buildApiUser).filter(Boolean);
|
|
||||||
|
|
||||||
const userBoosts = result.boosts.filter((boost) => boost.userId);
|
|
||||||
const boosterIds = userBoosts.map((boost) => boost.userId!.toString());
|
|
||||||
const boosters = buildCollectionByCallback(userBoosts, (boost) => (
|
|
||||||
[boost.userId!.toString(), boost.expires]
|
|
||||||
));
|
|
||||||
|
|
||||||
return {
|
|
||||||
count: result.count,
|
|
||||||
users,
|
|
||||||
boosters,
|
|
||||||
boosterIds,
|
|
||||||
nextOffset: result.nextOffset,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@ -253,6 +253,15 @@ export type ApiGame = {
|
|||||||
document?: ApiDocument;
|
document?: ApiDocument;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiGiveaway = {
|
||||||
|
quantity: number;
|
||||||
|
months: number;
|
||||||
|
untilDate: number;
|
||||||
|
isOnlyForNewSubscribers?: true;
|
||||||
|
countries?: string[];
|
||||||
|
channelIds: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiNewPoll = {
|
export type ApiNewPoll = {
|
||||||
summary: ApiPoll['summary'];
|
summary: ApiPoll['summary'];
|
||||||
quiz?: {
|
quiz?: {
|
||||||
@ -281,6 +290,8 @@ export interface ApiAction {
|
|||||||
months?: number;
|
months?: number;
|
||||||
topicEmojiIconId?: string;
|
topicEmojiIconId?: string;
|
||||||
isTopicAction?: boolean;
|
isTopicAction?: boolean;
|
||||||
|
slug?: string;
|
||||||
|
isGiveaway?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiWebPage {
|
export interface ApiWebPage {
|
||||||
@ -432,6 +443,7 @@ export type MediaContent = {
|
|||||||
location?: ApiLocation;
|
location?: ApiLocation;
|
||||||
game?: ApiGame;
|
game?: ApiGame;
|
||||||
storyData?: ApiMessageStoryData;
|
storyData?: ApiMessageStoryData;
|
||||||
|
giveaway?: ApiGiveaway;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ApiMessage {
|
export interface ApiMessage {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { ApiInvoiceContainer } from '../../types';
|
import type { ApiInvoiceContainer } from '../../types';
|
||||||
import type { ApiWebDocument } from './bots';
|
import type { ApiWebDocument } from './bots';
|
||||||
import type { ApiDocument, ApiMessageEntity, ApiPaymentCredentials } from './messages';
|
import type { ApiDocument, ApiMessageEntity, ApiPaymentCredentials } from './messages';
|
||||||
|
import type { StatisticsOverviewPercentage } from './statistics';
|
||||||
|
|
||||||
export interface ApiShippingAddress {
|
export interface ApiShippingAddress {
|
||||||
streetLine1: string;
|
streetLine1: string;
|
||||||
@ -77,3 +78,54 @@ export interface ApiPremiumSubscriptionOption {
|
|||||||
amount: string;
|
amount: string;
|
||||||
botUrl: string;
|
botUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ApiBoostsStatus = {
|
||||||
|
level: number;
|
||||||
|
currentLevelBoosts: number;
|
||||||
|
boosts: number;
|
||||||
|
nextLevelBoosts?: number;
|
||||||
|
hasMyBoost?: boolean;
|
||||||
|
boostUrl: string;
|
||||||
|
premiumSubscribers?: StatisticsOverviewPercentage;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiMyBoost = {
|
||||||
|
slot: number;
|
||||||
|
chatId?: string;
|
||||||
|
date: number;
|
||||||
|
expires: number;
|
||||||
|
cooldownUntil?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiGiveawayInfoActive = {
|
||||||
|
type: 'active';
|
||||||
|
isParticipating?: true;
|
||||||
|
isPreparingResults?: true;
|
||||||
|
startDate: number;
|
||||||
|
joinedTooEarlyDate?: number;
|
||||||
|
adminDisallowedChatId?: string;
|
||||||
|
disallowedCountry?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiGiveawayInfoResults = {
|
||||||
|
type: 'results';
|
||||||
|
isWinner?: true;
|
||||||
|
isRefunded?: true;
|
||||||
|
startDate: number;
|
||||||
|
finishDate: number;
|
||||||
|
giftCodeSlug?: string;
|
||||||
|
winnersCount: number;
|
||||||
|
activatedCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiGiveawayInfo = ApiGiveawayInfoActive | ApiGiveawayInfoResults;
|
||||||
|
|
||||||
|
export type ApiCheckedGiftCode = {
|
||||||
|
isFromGiveaway?: true;
|
||||||
|
fromId?: string;
|
||||||
|
giveawayMessageId?: number;
|
||||||
|
toId?: string;
|
||||||
|
date: number;
|
||||||
|
months: number;
|
||||||
|
usedAt?: number;
|
||||||
|
};
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import type { ApiPrivacySettings } from '../../types';
|
|||||||
import type {
|
import type {
|
||||||
ApiGeoPoint, ApiReaction, ApiReactionCount, MediaContent,
|
ApiGeoPoint, ApiReaction, ApiReactionCount, MediaContent,
|
||||||
} from './messages';
|
} from './messages';
|
||||||
import type { StatisticsOverviewPercentage } from './statistics';
|
|
||||||
|
|
||||||
export interface ApiStory {
|
export interface ApiStory {
|
||||||
'@type'?: 'story';
|
'@type'?: 'story';
|
||||||
@ -109,21 +108,3 @@ export type ApiMediaAreaSuggestedReaction = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ApiMediaArea = ApiMediaAreaVenue | ApiMediaAreaGeoPoint | ApiMediaAreaSuggestedReaction;
|
export type ApiMediaArea = ApiMediaAreaVenue | ApiMediaAreaGeoPoint | ApiMediaAreaSuggestedReaction;
|
||||||
|
|
||||||
export type ApiBoostsStatus = {
|
|
||||||
level: number;
|
|
||||||
currentLevelBoosts: number;
|
|
||||||
boosts: number;
|
|
||||||
nextLevelBoosts?: number;
|
|
||||||
hasMyBoost?: boolean;
|
|
||||||
boostUrl: string;
|
|
||||||
premiumSubscribers?: StatisticsOverviewPercentage;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ApiMyBoost = {
|
|
||||||
slot: number;
|
|
||||||
chatId?: string;
|
|
||||||
date: number;
|
|
||||||
expires: number;
|
|
||||||
cooldownUntil?: number;
|
|
||||||
};
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export { default as GiftPremiumModal } from '../components/main/premium/GiftPrem
|
|||||||
export { default as PremiumLimitReachedModal } from '../components/main/premium/common/PremiumLimitReachedModal';
|
export { default as PremiumLimitReachedModal } from '../components/main/premium/common/PremiumLimitReachedModal';
|
||||||
export { default as StatusPickerMenu } from '../components/left/main/StatusPickerMenu';
|
export { default as StatusPickerMenu } from '../components/left/main/StatusPickerMenu';
|
||||||
export { default as BoostModal } from '../components/modals/boost/BoostModal';
|
export { default as BoostModal } from '../components/modals/boost/BoostModal';
|
||||||
|
export { default as GiftCodeModal } from '../components/modals/giftcode/GiftCodeModal';
|
||||||
export { default as ChatlistModal } from '../components/modals/chatlist/ChatlistModal';
|
export { default as ChatlistModal } from '../components/modals/chatlist/ChatlistModal';
|
||||||
|
|
||||||
export { default as AboutAdsModal } from '../components/common/AboutAdsModal';
|
export { default as AboutAdsModal } from '../components/common/AboutAdsModal';
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.moreMenu {
|
.moreMenu, .copy {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0.5rem;
|
right: 0.5rem;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@ -12,22 +12,25 @@ import useLastCallback from '../../hooks/useLastCallback';
|
|||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import DropdownMenu from '../ui/DropdownMenu';
|
import DropdownMenu from '../ui/DropdownMenu';
|
||||||
import MenuItem from '../ui/MenuItem';
|
import MenuItem from '../ui/MenuItem';
|
||||||
|
import Icon from './Icon';
|
||||||
|
|
||||||
import styles from './InviteLink.module.scss';
|
import styles from './LinkField.module.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
title?: string;
|
title?: string;
|
||||||
inviteLink: string;
|
link: string;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
withShare?: boolean;
|
||||||
onRevoke?: VoidFunction;
|
onRevoke?: VoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InviteLink: FC<OwnProps> = ({
|
const InviteLink: FC<OwnProps> = ({
|
||||||
title,
|
title,
|
||||||
inviteLink,
|
link,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
className,
|
className,
|
||||||
|
withShare,
|
||||||
onRevoke,
|
onRevoke,
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
@ -35,20 +38,22 @@ const InviteLink: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
|
|
||||||
const copyLink = useLastCallback((link: string) => {
|
const isOnlyCopy = !onRevoke;
|
||||||
|
|
||||||
|
const copyLink = useLastCallback(() => {
|
||||||
copyTextToClipboard(link);
|
copyTextToClipboard(link);
|
||||||
showNotification({
|
showNotification({
|
||||||
message: lang('LinkCopied'),
|
message: lang('LinkCopied'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleCopyPrimaryClicked = useLastCallback(() => {
|
const handleCopyClick = useLastCallback(() => {
|
||||||
if (isDisabled) return;
|
if (isDisabled) return;
|
||||||
copyLink(inviteLink);
|
copyLink();
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleShare = useLastCallback(() => {
|
const handleShare = useLastCallback(() => {
|
||||||
openChatWithDraft({ text: inviteLink });
|
openChatWithDraft({ text: link });
|
||||||
});
|
});
|
||||||
|
|
||||||
const PrimaryLinkMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
|
const PrimaryLinkMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
|
||||||
@ -75,28 +80,43 @@ const InviteLink: FC<OwnProps> = ({
|
|||||||
<div className={styles.primaryLink}>
|
<div className={styles.primaryLink}>
|
||||||
<input
|
<input
|
||||||
className={buildClassName('form-control', styles.input)}
|
className={buildClassName('form-control', styles.input)}
|
||||||
value={inviteLink}
|
value={link}
|
||||||
readOnly
|
readOnly
|
||||||
onClick={handleCopyPrimaryClicked}
|
onClick={handleCopyClick}
|
||||||
/>
|
/>
|
||||||
<DropdownMenu
|
{isOnlyCopy ? (
|
||||||
className={styles.moreMenu}
|
<Button
|
||||||
trigger={PrimaryLinkMenuButton}
|
color="translucent"
|
||||||
positionX="right"
|
className={styles.copy}
|
||||||
>
|
size="smaller"
|
||||||
<MenuItem icon="copy" onClick={handleCopyPrimaryClicked} disabled={isDisabled}>{lang('Copy')}</MenuItem>
|
round
|
||||||
{onRevoke && (
|
disabled={isDisabled}
|
||||||
<MenuItem icon="delete" onClick={onRevoke} destructive>{lang('RevokeButton')}</MenuItem>
|
onClick={handleCopyClick}
|
||||||
)}
|
>
|
||||||
</DropdownMenu>
|
<Icon name="copy" />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<DropdownMenu
|
||||||
|
className={styles.moreMenu}
|
||||||
|
trigger={PrimaryLinkMenuButton}
|
||||||
|
positionX="right"
|
||||||
|
>
|
||||||
|
<MenuItem icon="copy" onClick={handleCopyClick} disabled={isDisabled}>{lang('Copy')}</MenuItem>
|
||||||
|
{onRevoke && (
|
||||||
|
<MenuItem icon="delete" onClick={onRevoke} destructive>{lang('RevokeButton')}</MenuItem>
|
||||||
|
)}
|
||||||
|
</DropdownMenu>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
{withShare && (
|
||||||
size="smaller"
|
<Button
|
||||||
disabled={isDisabled}
|
size="smaller"
|
||||||
onClick={handleShare}
|
disabled={isDisabled}
|
||||||
>
|
onClick={handleShare}
|
||||||
{lang('FolderLinkScreen.LinkActionShare')}
|
>
|
||||||
</Button>
|
{lang('FolderLinkScreen.LinkActionShare')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -51,6 +51,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.fluid {
|
||||||
|
max-width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
.SearchInput & {
|
.SearchInput & {
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@ -25,6 +25,7 @@ type OwnProps = {
|
|||||||
forceShowSelf?: boolean;
|
forceShowSelf?: boolean;
|
||||||
clickArg?: any;
|
clickArg?: any;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
fluid?: boolean;
|
||||||
onClick: (arg: any) => void;
|
onClick: (arg: any) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
|
|||||||
chat,
|
chat,
|
||||||
user,
|
user,
|
||||||
className,
|
className,
|
||||||
|
fluid,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}) => {
|
||||||
@ -81,6 +83,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
|
|||||||
chat?.isForum && 'forum-avatar',
|
chat?.isForum && 'forum-avatar',
|
||||||
isMinimized && 'minimized',
|
isMinimized && 'minimized',
|
||||||
canClose && 'closeable',
|
canClose && 'closeable',
|
||||||
|
fluid && 'fluid',
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -78,7 +78,7 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="settings-item">
|
<div className="settings-item">
|
||||||
<ListItem
|
<ListItem
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
onClick={() => requestConfetti()}
|
onClick={() => requestConfetti({})}
|
||||||
icon="animations"
|
icon="animations"
|
||||||
>
|
>
|
||||||
<div className="title">Launch some confetti!</div>
|
<div className="title">Launch some confetti!</div>
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import useLang from '../../../../hooks/useLang';
|
|||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import AnimatedIcon from '../../../common/AnimatedIcon';
|
import AnimatedIcon from '../../../common/AnimatedIcon';
|
||||||
import InviteLink from '../../../common/InviteLink';
|
import LinkField from '../../../common/LinkField';
|
||||||
import Picker from '../../../common/Picker';
|
import Picker from '../../../common/Picker';
|
||||||
import FloatingActionButton from '../../../ui/FloatingActionButton';
|
import FloatingActionButton from '../../../ui/FloatingActionButton';
|
||||||
import Spinner from '../../../ui/Spinner';
|
import Spinner from '../../../ui/Spinner';
|
||||||
@ -159,9 +159,10 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<InviteLink
|
<LinkField
|
||||||
className="settings-item"
|
className="settings-item"
|
||||||
inviteLink={!url ? lang('Loading') : url}
|
link={!url ? lang('Loading') : url}
|
||||||
|
withShare
|
||||||
onRevoke={handleRevoke}
|
onRevoke={handleRevoke}
|
||||||
isDisabled={!chatsCount || isTouched}
|
isDisabled={!chatsCount || isTouched}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -78,6 +78,7 @@ import MiddleColumn from '../middle/MiddleColumn';
|
|||||||
import AttachBotInstallModal from '../modals/attachBotInstall/AttachBotInstallModal.async';
|
import AttachBotInstallModal from '../modals/attachBotInstall/AttachBotInstallModal.async';
|
||||||
import BoostModal from '../modals/boost/BoostModal.async';
|
import BoostModal from '../modals/boost/BoostModal.async';
|
||||||
import ChatlistModal from '../modals/chatlist/ChatlistModal.async';
|
import ChatlistModal from '../modals/chatlist/ChatlistModal.async';
|
||||||
|
import GiftCodeModal from '../modals/giftcode/GiftCodeModal.async';
|
||||||
import MapModal from '../modals/map/MapModal.async';
|
import MapModal from '../modals/map/MapModal.async';
|
||||||
import UrlAuthModal from '../modals/urlAuth/UrlAuthModal.async';
|
import UrlAuthModal from '../modals/urlAuth/UrlAuthModal.async';
|
||||||
import WebAppModal from '../modals/webApp/WebAppModal.async';
|
import WebAppModal from '../modals/webApp/WebAppModal.async';
|
||||||
@ -110,6 +111,7 @@ export interface OwnProps {
|
|||||||
type StateProps = {
|
type StateProps = {
|
||||||
isMasterTab?: boolean;
|
isMasterTab?: boolean;
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
|
currentUserId?: string;
|
||||||
isLeftColumnOpen: boolean;
|
isLeftColumnOpen: boolean;
|
||||||
isMiddleColumnOpen: boolean;
|
isMiddleColumnOpen: boolean;
|
||||||
isRightColumnOpen: boolean;
|
isRightColumnOpen: boolean;
|
||||||
@ -155,6 +157,7 @@ type StateProps = {
|
|||||||
isCurrentUserPremium?: boolean;
|
isCurrentUserPremium?: boolean;
|
||||||
chatlistModal?: TabState['chatlistModal'];
|
chatlistModal?: TabState['chatlistModal'];
|
||||||
boostModal?: TabState['boostModal'];
|
boostModal?: TabState['boostModal'];
|
||||||
|
giftCodeModal?: TabState['giftCodeModal'];
|
||||||
noRightColumnAnimation?: boolean;
|
noRightColumnAnimation?: boolean;
|
||||||
withInterfaceAnimations?: boolean;
|
withInterfaceAnimations?: boolean;
|
||||||
isSynced?: boolean;
|
isSynced?: boolean;
|
||||||
@ -174,6 +177,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
isMediaViewerOpen,
|
isMediaViewerOpen,
|
||||||
isStoryViewerOpen,
|
isStoryViewerOpen,
|
||||||
isForwardModalOpen,
|
isForwardModalOpen,
|
||||||
|
currentUserId,
|
||||||
hasNotifications,
|
hasNotifications,
|
||||||
hasDialogs,
|
hasDialogs,
|
||||||
audioMessage,
|
audioMessage,
|
||||||
@ -214,6 +218,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
deleteFolderDialog,
|
deleteFolderDialog,
|
||||||
isMasterTab,
|
isMasterTab,
|
||||||
chatlistModal,
|
chatlistModal,
|
||||||
|
giftCodeModal,
|
||||||
boostModal,
|
boostModal,
|
||||||
noRightColumnAnimation,
|
noRightColumnAnimation,
|
||||||
isSynced,
|
isSynced,
|
||||||
@ -558,6 +563,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
isByPhoneNumber={newContactByPhoneNumber}
|
isByPhoneNumber={newContactByPhoneNumber}
|
||||||
/>
|
/>
|
||||||
<BoostModal info={boostModal} />
|
<BoostModal info={boostModal} />
|
||||||
|
<GiftCodeModal modal={giftCodeModal} currentUserId={currentUserId} />
|
||||||
<ChatlistModal info={chatlistModal} />
|
<ChatlistModal info={chatlistModal} />
|
||||||
<GameModal openedGame={openedGame} gameTitle={gameTitle} />
|
<GameModal openedGame={openedGame} gameTitle={gameTitle} />
|
||||||
<WebAppModal webApp={webApp} />
|
<WebAppModal webApp={webApp} />
|
||||||
@ -592,6 +598,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
language, wasTimeFormatSetManually,
|
language, wasTimeFormatSetManually,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
currentUserId,
|
||||||
} = global;
|
} = global;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -621,6 +628,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
deleteFolderDialogModal,
|
deleteFolderDialogModal,
|
||||||
chatlistModal,
|
chatlistModal,
|
||||||
boostModal,
|
boostModal,
|
||||||
|
giftCodeModal,
|
||||||
} = selectTabState(global);
|
} = selectTabState(global);
|
||||||
|
|
||||||
const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer;
|
const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer;
|
||||||
@ -637,6 +645,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const deleteFolderDialog = deleteFolderDialogModal ? selectChatFolder(global, deleteFolderDialogModal) : undefined;
|
const deleteFolderDialog = deleteFolderDialogModal ? selectChatFolder(global, deleteFolderDialogModal) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
currentUserId,
|
||||||
isLeftColumnOpen: isLeftColumnShown,
|
isLeftColumnOpen: isLeftColumnShown,
|
||||||
isMiddleColumnOpen: Boolean(chatId),
|
isMiddleColumnOpen: Boolean(chatId),
|
||||||
isRightColumnOpen: selectIsRightColumnShown(global, isMobile),
|
isRightColumnOpen: selectIsRightColumnShown(global, isMobile),
|
||||||
@ -684,6 +693,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
requestedDraft,
|
requestedDraft,
|
||||||
chatlistModal,
|
chatlistModal,
|
||||||
boostModal,
|
boostModal,
|
||||||
|
giftCodeModal,
|
||||||
noRightColumnAnimation,
|
noRightColumnAnimation,
|
||||||
isSynced: global.isSynced,
|
isSynced: global.isSynced,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,12 +12,13 @@ import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
|||||||
import type { FocusDirection } from '../../types';
|
import type { FocusDirection } from '../../types';
|
||||||
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
||||||
|
|
||||||
import { getMessageHtmlId, isChatChannel } from '../../global/helpers';
|
import { getChatTitle, getMessageHtmlId, isChatChannel } from '../../global/helpers';
|
||||||
import { getMessageReplyInfo } from '../../global/helpers/replies';
|
import { getMessageReplyInfo } from '../../global/helpers/replies';
|
||||||
import {
|
import {
|
||||||
selectCanPlayAnimatedEmojis,
|
selectCanPlayAnimatedEmojis,
|
||||||
selectChat,
|
selectChat,
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
|
selectGiftStickerForDuration,
|
||||||
selectIsMessageFocused,
|
selectIsMessageFocused,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectTopicFromMessage,
|
selectTopicFromMessage,
|
||||||
@ -25,6 +26,7 @@ import {
|
|||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { renderActionMessageText } from '../common/helpers/renderActionMessageText';
|
import { renderActionMessageText } from '../common/helpers/renderActionMessageText';
|
||||||
|
import renderText from '../common/helpers/renderText';
|
||||||
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
||||||
|
|
||||||
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
||||||
@ -61,6 +63,7 @@ type StateProps = {
|
|||||||
targetUserIds?: string[];
|
targetUserIds?: string[];
|
||||||
targetMessage?: ApiMessage;
|
targetMessage?: ApiMessage;
|
||||||
targetChatId?: string;
|
targetChatId?: string;
|
||||||
|
targetChat?: ApiChat;
|
||||||
isFocused: boolean;
|
isFocused: boolean;
|
||||||
topic?: ApiTopic;
|
topic?: ApiTopic;
|
||||||
focusDirection?: FocusDirection;
|
focusDirection?: FocusDirection;
|
||||||
@ -82,6 +85,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
targetUserIds,
|
targetUserIds,
|
||||||
targetMessage,
|
targetMessage,
|
||||||
targetChatId,
|
targetChatId,
|
||||||
|
targetChat,
|
||||||
isFocused,
|
isFocused,
|
||||||
focusDirection,
|
focusDirection,
|
||||||
noFocusHighlight,
|
noFocusHighlight,
|
||||||
@ -95,7 +99,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
observeIntersectionForPlaying,
|
observeIntersectionForPlaying,
|
||||||
onPinnedIntersectionChange,
|
onPinnedIntersectionChange,
|
||||||
}) => {
|
}) => {
|
||||||
const { openPremiumModal, requestConfetti } = getActions();
|
const { openPremiumModal, requestConfetti, checkGiftCode } = getActions();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
@ -121,6 +125,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
const noAppearanceAnimation = appearanceOrder <= 0;
|
const noAppearanceAnimation = appearanceOrder <= 0;
|
||||||
const [isShown, markShown] = useFlag(noAppearanceAnimation);
|
const [isShown, markShown] = useFlag(noAppearanceAnimation);
|
||||||
const isGift = Boolean(message.content.action?.text.startsWith('ActionGift'));
|
const isGift = Boolean(message.content.action?.text.startsWith('ActionGift'));
|
||||||
|
const isGiftCode = Boolean(message.content.action?.text.startsWith('BoostingReceivedGift'));
|
||||||
const isSuggestedAvatar = message.content.action?.type === 'suggestProfilePhoto' && message.content.action!.photo;
|
const isSuggestedAvatar = message.content.action?.type === 'suggestProfilePhoto' && message.content.action!.photo;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -141,7 +146,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isVisible && shouldShowConfettiRef.current) {
|
if (isVisible && shouldShowConfettiRef.current) {
|
||||||
shouldShowConfettiRef.current = false;
|
shouldShowConfettiRef.current = false;
|
||||||
requestConfetti();
|
requestConfetti({});
|
||||||
}
|
}
|
||||||
}, [isVisible, requestConfetti]);
|
}, [isVisible, requestConfetti]);
|
||||||
|
|
||||||
@ -195,6 +200,12 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleGiftCodeClick = () => {
|
||||||
|
const slug = message.content.action?.slug;
|
||||||
|
if (!slug) return;
|
||||||
|
checkGiftCode({ slug });
|
||||||
|
};
|
||||||
|
|
||||||
// TODO Refactoring for action rendering
|
// TODO Refactoring for action rendering
|
||||||
const shouldSkipRender = isInsideTopic && message.content.action?.text === 'TopicWasCreatedAction';
|
const shouldSkipRender = isInsideTopic && message.content.action?.text === 'TopicWasCreatedAction';
|
||||||
if (shouldSkipRender) {
|
if (shouldSkipRender) {
|
||||||
@ -223,13 +234,47 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderGiftCode() {
|
||||||
|
const isFromGiveaway = message.content.action?.isGiveaway;
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="action-message-gift action-message-gift-code"
|
||||||
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
|
onClick={handleGiftCodeClick}
|
||||||
|
>
|
||||||
|
<AnimatedIconFromSticker
|
||||||
|
key={message.id}
|
||||||
|
sticker={premiumGiftSticker}
|
||||||
|
play={canPlayAnimatedEmojis}
|
||||||
|
noLoop
|
||||||
|
nonInteractive
|
||||||
|
/>
|
||||||
|
<strong>{lang('BoostingUnclaimedPrize')}</strong>
|
||||||
|
<span className="action-message-subtitle">
|
||||||
|
{renderText(lang(isFromGiveaway ? 'BoostingReceivedGiftFrom' : 'BoostingYouHaveUnclaimedPrize',
|
||||||
|
getChatTitle(lang, targetChat!)),
|
||||||
|
['simple_markdown'])}
|
||||||
|
</span>
|
||||||
|
<span className="action-message-subtitle">
|
||||||
|
{renderText(lang(
|
||||||
|
'BoostingUnclaimedPrizeDuration',
|
||||||
|
lang('Months', message.content.action?.months, 'i'),
|
||||||
|
), ['simple_markdown'])}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="action-message-button">{lang('BoostingReceivedGiftOpenBtn')}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const className = buildClassName(
|
const className = buildClassName(
|
||||||
'ActionMessage message-list-item',
|
'ActionMessage message-list-item',
|
||||||
isFocused && !noFocusHighlight && 'focused',
|
isFocused && !noFocusHighlight && 'focused',
|
||||||
(isGift || isSuggestedAvatar) && 'centered-action',
|
(isGift || isSuggestedAvatar) && 'centered-action',
|
||||||
isContextMenuShown && 'has-menu-open',
|
isContextMenuShown && 'has-menu-open',
|
||||||
isLastInList && 'last-in-list',
|
isLastInList && 'last-in-list',
|
||||||
!isGift && !isSuggestedAvatar && 'in-one-row',
|
!isGift && !isSuggestedAvatar && !isGiftCode && 'in-one-row',
|
||||||
transitionClassNames,
|
transitionClassNames,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -243,8 +288,9 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
>
|
>
|
||||||
{!isSuggestedAvatar && <span className="action-message-content">{renderContent()}</span>}
|
{!isSuggestedAvatar && !isGiftCode && <span className="action-message-content">{renderContent()}</span>}
|
||||||
{isGift && renderGift()}
|
{isGift && renderGift()}
|
||||||
|
{isGiftCode && renderGiftCode()}
|
||||||
{isSuggestedAvatar && (
|
{isSuggestedAvatar && (
|
||||||
<ActionMessageSuggestedAvatar
|
<ActionMessageSuggestedAvatar
|
||||||
message={message}
|
message={message}
|
||||||
@ -288,12 +334,17 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const isChat = chat && (isChatChannel(chat) || userId === chatId);
|
const isChat = chat && (isChatChannel(chat) || userId === chatId);
|
||||||
const senderUser = !isChat && userId ? selectUser(global, userId) : undefined;
|
const senderUser = !isChat && userId ? selectUser(global, userId) : undefined;
|
||||||
const senderChat = isChat ? chat : undefined;
|
const senderChat = isChat ? chat : undefined;
|
||||||
const premiumGiftSticker = global.premiumGifts?.stickers?.[0];
|
|
||||||
|
const targetChat = targetChatId ? selectChat(global, targetChatId) : undefined;
|
||||||
|
|
||||||
|
const giftDuration = content.action?.months;
|
||||||
|
const premiumGiftSticker = selectGiftStickerForDuration(global, giftDuration);
|
||||||
const topic = selectTopicFromMessage(global, message);
|
const topic = selectTopicFromMessage(global, message);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
senderUser,
|
senderUser,
|
||||||
senderChat,
|
senderChat,
|
||||||
|
targetChat,
|
||||||
targetChatId,
|
targetChatId,
|
||||||
targetUserIds,
|
targetUserIds,
|
||||||
targetMessage,
|
targetMessage,
|
||||||
|
|||||||
@ -278,6 +278,17 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-message-gift-code {
|
||||||
|
width: 20rem;
|
||||||
|
margin-inline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-message-subtitle {
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-weight: normal;
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
|
||||||
.action-message-suggested-avatar {
|
.action-message-suggested-avatar {
|
||||||
max-width: 16rem;
|
max-width: 16rem;
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
|
|||||||
@ -119,7 +119,8 @@
|
|||||||
.audio &,
|
.audio &,
|
||||||
.voice &,
|
.voice &,
|
||||||
.poll &,
|
.poll &,
|
||||||
.text & {
|
.text &,
|
||||||
|
.giveaway & {
|
||||||
border-top: 1px solid var(--color-borders);
|
border-top: 1px solid var(--color-borders);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,6 +137,7 @@
|
|||||||
.message-content.audio &,
|
.message-content.audio &,
|
||||||
.message-content.voice &,
|
.message-content.voice &,
|
||||||
.message-content.poll &,
|
.message-content.poll &,
|
||||||
|
.message-content.giveaway &,
|
||||||
.message-content.has-solid-background.text &,
|
.message-content.has-solid-background.text &,
|
||||||
.message-content.has-solid-background.is-forwarded & {
|
.message-content.has-solid-background.is-forwarded & {
|
||||||
width: calc(100% + 1rem);
|
width: calc(100% + 1rem);
|
||||||
|
|||||||
64
src/components/middle/message/Giveaway.module.scss
Normal file
64
src/components/middle/message/Giveaway.module.scss
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.count {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
border-radius: 1rem;
|
||||||
|
padding: 0.0625rem 0.5rem;
|
||||||
|
border: 1px solid var(--background-color);
|
||||||
|
line-height: 1.25;
|
||||||
|
|
||||||
|
:global(.theme-dark .own) & {
|
||||||
|
background-color: var(--color-text);
|
||||||
|
color: var(--background-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
line-height: 1.25;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channels {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-block: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel {
|
||||||
|
background-color: var(--accent-background-color);
|
||||||
|
color: var(--accent-color);
|
||||||
|
margin: unset;
|
||||||
|
|
||||||
|
&:hover, &:active, &:focus {
|
||||||
|
background-color: var(--accent-background-active-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
258
src/components/middle/message/Giveaway.tsx
Normal file
258
src/components/middle/message/Giveaway.tsx
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
import React, {
|
||||||
|
memo, useMemo, useRef, useState,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
|
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ApiChat, ApiGiveawayInfo, ApiMessage, ApiPeer, ApiSticker,
|
||||||
|
} from '../../../api/types';
|
||||||
|
|
||||||
|
import { getChatTitle, getUserFullName, isApiPeerChat } from '../../../global/helpers';
|
||||||
|
import {
|
||||||
|
selectCanPlayAnimatedEmojis,
|
||||||
|
selectChat,
|
||||||
|
selectForwardedSender,
|
||||||
|
selectGiftStickerForDuration,
|
||||||
|
} from '../../../global/selectors';
|
||||||
|
import { formatDateAtTime, formatDateTimeToString } from '../../../util/dateFormat';
|
||||||
|
import { isoToEmoji } from '../../../util/emoji';
|
||||||
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
|
import { callApi } from '../../../api/gramjs';
|
||||||
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import AnimatedIconFromSticker from '../../common/AnimatedIconFromSticker';
|
||||||
|
import PickerSelectedItem from '../../common/PickerSelectedItem';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
|
|
||||||
|
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 Giveaway = ({
|
||||||
|
chat,
|
||||||
|
sender,
|
||||||
|
message,
|
||||||
|
canPlayAnimatedEmojis,
|
||||||
|
giftSticker,
|
||||||
|
}: OwnProps & StateProps) => {
|
||||||
|
const { openChat } = getActions();
|
||||||
|
|
||||||
|
const isLoadingInfo = useRef(false);
|
||||||
|
const [giveawayInfo, setGiveawayInfo] = useState<ApiGiveawayInfo | undefined>();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
const {
|
||||||
|
months, quantity, channelIds, untilDate, countries,
|
||||||
|
} = message.content.giveaway!;
|
||||||
|
|
||||||
|
const hasEnded = getServerTime() > untilDate;
|
||||||
|
|
||||||
|
const countryList = useMemo(() => {
|
||||||
|
const translatedNames = new Intl.DisplayNames([lang.code!, 'en'].filter(Boolean), { type: 'region' });
|
||||||
|
return countries?.map((countryCode) => (
|
||||||
|
`${isoToEmoji(countryCode)}${NBSP}${translatedNames.of(countryCode)}`
|
||||||
|
)).join(', ');
|
||||||
|
}, [countries, lang.code]);
|
||||||
|
|
||||||
|
const handleChannelClick = 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 renderGiveawayInfo() {
|
||||||
|
if (!sender || !giveawayInfo) return undefined;
|
||||||
|
const isResults = 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 = channelIds.length ? channelIds.length - 1 : 0;
|
||||||
|
const otherChannelsString = lang('Chat.Giveaway.Info.OtherChannels', otherChannelsCount);
|
||||||
|
const isSeveral = otherChannelsCount > 0;
|
||||||
|
|
||||||
|
const firstKey = isResults ? 'BoostingGiveawayHowItWorksTextEnd' : 'BoostingGiveawayHowItWorksText';
|
||||||
|
const firstParagraph = lang(firstKey, [chatTitle, quantity, duration], undefined, quantity);
|
||||||
|
|
||||||
|
let secondKey = '';
|
||||||
|
if (isResults) {
|
||||||
|
secondKey = isSeveral ? 'BoostingGiveawayHowItWorksSubTextSeveralEnd' : 'BoostingGiveawayHowItWorksSubTextEnd';
|
||||||
|
} else {
|
||||||
|
secondKey = isSeveral ? 'BoostingGiveawayHowItWorksSubTextSeveral' : 'BoostingGiveawayHowItWorksSubText';
|
||||||
|
}
|
||||||
|
let secondParagraph = lang(secondKey, [endDate, quantity, chatTitle, otherChannelsCount], undefined, quantity);
|
||||||
|
if (isResults && giveawayInfo.activatedCount) {
|
||||||
|
secondParagraph += ` ${lang('BoostingGiveawayUsedLinksPlural', giveawayInfo.activatedCount)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastParagraph = '';
|
||||||
|
if (isResults && giveawayInfo.isRefunded) {
|
||||||
|
lastParagraph = lang('BoostingGiveawayCanceledByPayment');
|
||||||
|
} else if (isResults) {
|
||||||
|
lastParagraph = lang(giveawayInfo.isWinner ? 'BoostingGiveawayYouWon' : 'BoostingGiveawayYouNotWon');
|
||||||
|
} 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 (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
{renderText(firstParagraph, ['simple_markdown'])}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{renderText(secondParagraph, ['simple_markdown'])}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{renderText(lastParagraph, ['simple_markdown'])}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<div className={styles.gift}>
|
||||||
|
<AnimatedIconFromSticker
|
||||||
|
key={message.id}
|
||||||
|
sticker={giftSticker}
|
||||||
|
play={canPlayAnimatedEmojis && hasEnded}
|
||||||
|
noLoop
|
||||||
|
nonInteractive
|
||||||
|
size={GIFT_STICKER_SIZE}
|
||||||
|
/>
|
||||||
|
<span className={styles.count}>
|
||||||
|
{`x${quantity}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.section}>
|
||||||
|
<strong className={styles.title}>
|
||||||
|
{renderText(lang('BoostingGiveawayPrizes'), ['simple_markdown'])}
|
||||||
|
</strong>
|
||||||
|
<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.channels}>
|
||||||
|
{channelIds.map((channelId) => (
|
||||||
|
<PickerSelectedItem
|
||||||
|
peerId={channelId}
|
||||||
|
forceShowSelf
|
||||||
|
fluid
|
||||||
|
className={styles.channel}
|
||||||
|
clickArg={channelId}
|
||||||
|
onClick={handleChannelClick}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{countries?.length && (
|
||||||
|
<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>
|
||||||
|
<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 duration = message.content.giveaway!.months;
|
||||||
|
const chat = selectChat(global, message.chatId)!;
|
||||||
|
const sender = selectChat(global, message.content.giveaway?.channelIds[0]!)
|
||||||
|
|| selectForwardedSender(global, message) || chat;
|
||||||
|
|
||||||
|
return {
|
||||||
|
chat,
|
||||||
|
sender,
|
||||||
|
giftSticker: selectGiftStickerForDuration(global, duration),
|
||||||
|
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(Giveaway));
|
||||||
@ -152,6 +152,7 @@ import CommentButton from './CommentButton';
|
|||||||
import Contact from './Contact';
|
import Contact from './Contact';
|
||||||
import ContextMenuContainer from './ContextMenuContainer.async';
|
import ContextMenuContainer from './ContextMenuContainer.async';
|
||||||
import Game from './Game';
|
import Game from './Game';
|
||||||
|
import Giveaway from './Giveaway';
|
||||||
import InlineButtons from './InlineButtons';
|
import InlineButtons from './InlineButtons';
|
||||||
import Invoice from './Invoice';
|
import Invoice from './Invoice';
|
||||||
import InvoiceMediaPreview from './InvoiceMediaPreview';
|
import InvoiceMediaPreview from './InvoiceMediaPreview';
|
||||||
@ -630,7 +631,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
text, photo, video, audio,
|
text, photo, video, audio,
|
||||||
voice, document, sticker, contact,
|
voice, document, sticker, contact,
|
||||||
poll, webPage, invoice, location,
|
poll, webPage, invoice, location,
|
||||||
action, game, storyData,
|
action, game, storyData, giveaway,
|
||||||
} = getMessageContent(message);
|
} = getMessageContent(message);
|
||||||
|
|
||||||
const { replyToMsgId, replyToPeerId, isQuote } = messageReplyInfo || {};
|
const { replyToMsgId, replyToPeerId, isQuote } = messageReplyInfo || {};
|
||||||
@ -1137,6 +1138,9 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
{poll && (
|
{poll && (
|
||||||
<Poll message={message} poll={poll} onSendVote={handleVoteSend} />
|
<Poll message={message} poll={poll} onSendVote={handleVoteSend} />
|
||||||
)}
|
)}
|
||||||
|
{giveaway && (
|
||||||
|
<Giveaway message={message} />
|
||||||
|
)}
|
||||||
{game && (
|
{game && (
|
||||||
<Game
|
<Game
|
||||||
message={message}
|
message={message}
|
||||||
|
|||||||
@ -90,7 +90,7 @@ const Poll: FC<OwnProps & StateProps> = ({
|
|||||||
const chosen = poll.results.results?.find((result) => result.isChosen);
|
const chosen = poll.results.results?.find((result) => result.isChosen);
|
||||||
if (isSubmitting && chosen) {
|
if (isSubmitting && chosen) {
|
||||||
if (chosen.isCorrect) {
|
if (chosen.isCorrect) {
|
||||||
requestConfetti();
|
requestConfetti({});
|
||||||
}
|
}
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -825,7 +825,8 @@
|
|||||||
.forwarded-message {
|
.forwarded-message {
|
||||||
.message-content.contact &,
|
.message-content.contact &,
|
||||||
.message-content.voice &,
|
.message-content.voice &,
|
||||||
.message-content.poll & {
|
.message-content.poll &,
|
||||||
|
.message-content.giveaway & {
|
||||||
// MessageOutgoingStatus's icon needs more space
|
// MessageOutgoingStatus's icon needs more space
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ export function buildContentClassName(
|
|||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
text, photo, video, audio, voice, document, poll, webPage, contact, location, invoice, storyData,
|
text, photo, video, audio, voice, document, poll, webPage, contact, location, invoice, storyData, giveaway,
|
||||||
} = getMessageContent(message);
|
} = getMessageContent(message);
|
||||||
|
|
||||||
const classNames = [MESSAGE_CONTENT_CLASS_NAME];
|
const classNames = [MESSAGE_CONTENT_CLASS_NAME];
|
||||||
@ -87,6 +87,8 @@ export function buildContentClassName(
|
|||||||
classNames.push('contact');
|
classNames.push('contact');
|
||||||
} else if (poll) {
|
} else if (poll) {
|
||||||
classNames.push('poll');
|
classNames.push('poll');
|
||||||
|
} else if (giveaway) {
|
||||||
|
classNames.push('giveaway');
|
||||||
} else if (webPage) {
|
} else if (webPage) {
|
||||||
classNames.push('web-page');
|
classNames.push('web-page');
|
||||||
|
|
||||||
|
|||||||
@ -172,7 +172,7 @@ const BoostModal = ({
|
|||||||
const handleApplyBoost = useLastCallback(() => {
|
const handleApplyBoost = useLastCallback(() => {
|
||||||
closeReplaceModal();
|
closeReplaceModal();
|
||||||
applyBoost({ chatId: chat!.id, slots: [boost!.slot] });
|
applyBoost({ chatId: chat!.id, slots: [boost!.slot] });
|
||||||
requestConfetti();
|
requestConfetti({});
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleProceedPremium = useLastCallback(() => {
|
const handleProceedPremium = useLastCallback(() => {
|
||||||
|
|||||||
18
src/components/modals/giftcode/GiftCodeModal.async.tsx
Normal file
18
src/components/modals/giftcode/GiftCodeModal.async.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { OwnProps } from './GiftCodeModal';
|
||||||
|
|
||||||
|
import { Bundles } from '../../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const GiftCodeModalAsync: FC<OwnProps> = (props) => {
|
||||||
|
const { modal } = props;
|
||||||
|
const GiftCodeModal = useModuleLoader(Bundles.Extra, 'GiftCodeModal', !modal);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return GiftCodeModal ? <GiftCodeModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GiftCodeModalAsync;
|
||||||
38
src/components/modals/giftcode/GiftCodeModal.module.scss
Normal file
38
src/components/modals/giftcode/GiftCodeModal.module.scss
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
max-height: min(92vh, 40rem) !important;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable {
|
||||||
|
color: var(--color-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
background-color: var(--color-background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td {
|
||||||
|
border: 1px solid var(--color-borders);
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item {
|
||||||
|
margin: 0;
|
||||||
|
width: fit-content;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 6.25rem;
|
||||||
|
height: 6.25rem;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
160
src/components/modals/giftcode/GiftCodeModal.tsx
Normal file
160
src/components/modals/giftcode/GiftCodeModal.tsx
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
|
import type { TabState } from '../../../global/types';
|
||||||
|
|
||||||
|
import { TME_LINK_PREFIX } from '../../../config';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { formatDateTimeToString } from '../../../util/dateFormat';
|
||||||
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import LinkField from '../../common/LinkField';
|
||||||
|
import PickerSelectedItem from '../../common/PickerSelectedItem';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import Modal from '../../ui/Modal';
|
||||||
|
|
||||||
|
import styles from './GiftCodeModal.module.scss';
|
||||||
|
|
||||||
|
import PremiumLogo from '../../../assets/premium/PremiumLogo.svg';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
currentUserId?: string;
|
||||||
|
modal: TabState['giftCodeModal'];
|
||||||
|
};
|
||||||
|
|
||||||
|
const GIFTCODE_PATH = 'giftcode';
|
||||||
|
|
||||||
|
const GiftCodeModal = ({
|
||||||
|
currentUserId,
|
||||||
|
modal,
|
||||||
|
}: OwnProps) => {
|
||||||
|
const {
|
||||||
|
closeGiftCodeModal, openChat, applyGiftCode, focusMessage,
|
||||||
|
} = getActions();
|
||||||
|
const lang = useLang();
|
||||||
|
const isOpen = Boolean(modal);
|
||||||
|
|
||||||
|
const canUse = (!modal?.info.toId || modal?.info.toId === currentUserId) && !modal?.info.usedAt;
|
||||||
|
|
||||||
|
const handleOpenChat = useLastCallback((peerId: string) => {
|
||||||
|
openChat({ id: peerId });
|
||||||
|
closeGiftCodeModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleOpenGiveaway = useLastCallback(() => {
|
||||||
|
if (!modal || !modal.info.giveawayMessageId) return;
|
||||||
|
focusMessage({
|
||||||
|
chatId: modal.info.fromId!,
|
||||||
|
messageId: modal.info.giveawayMessageId,
|
||||||
|
});
|
||||||
|
closeGiftCodeModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleButtonClick = useLastCallback(() => {
|
||||||
|
if (canUse) {
|
||||||
|
applyGiftCode({ slug: modal!.slug });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
closeGiftCodeModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderContent() {
|
||||||
|
if (!modal) return undefined;
|
||||||
|
const { slug, info } = modal;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<img className={styles.logo} src={PremiumLogo} alt="" draggable={false} />
|
||||||
|
<p className={styles.centered}>{renderText(lang('lng_gift_link_about'), ['simple_markdown'])}</p>
|
||||||
|
<LinkField title="BoostingGiftLink" link={`${TME_LINK_PREFIX}/${GIFTCODE_PATH}/${slug}`} />
|
||||||
|
<table className={styles.table}>
|
||||||
|
<tr>
|
||||||
|
<td className={styles.title}>{lang('BoostingFrom')}</td>
|
||||||
|
<td>
|
||||||
|
{info.fromId ? (
|
||||||
|
<PickerSelectedItem
|
||||||
|
peerId={info.fromId}
|
||||||
|
className={styles.chatItem}
|
||||||
|
forceShowSelf
|
||||||
|
fluid
|
||||||
|
clickArg={info.fromId}
|
||||||
|
onClick={handleOpenChat}
|
||||||
|
/>
|
||||||
|
) : lang('BoostingNoRecipient')}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className={styles.title}>
|
||||||
|
{lang('BoostingTo')}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{info.toId ? (
|
||||||
|
<PickerSelectedItem
|
||||||
|
peerId={info.toId}
|
||||||
|
className={styles.chatItem}
|
||||||
|
forceShowSelf
|
||||||
|
fluid
|
||||||
|
clickArg={info.toId}
|
||||||
|
onClick={handleOpenChat}
|
||||||
|
/>
|
||||||
|
) : lang('BoostingNoRecipient')}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className={styles.title}>
|
||||||
|
{lang('BoostingGift')}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{lang('BoostingTelegramPremiumFor', lang('Months', info.months, 'i'))}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className={styles.title}>
|
||||||
|
{lang('BoostingReason')}
|
||||||
|
</td>
|
||||||
|
<td className={buildClassName(info.giveawayMessageId && styles.clickable)} onClick={handleOpenGiveaway}>
|
||||||
|
{info.isFromGiveaway && !info.toId ? lang('BoostingIncompleteGiveaway')
|
||||||
|
: lang(info.isFromGiveaway ? 'BoostingGiveaway' : 'BoostingYouWereSelected')}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className={styles.title}>
|
||||||
|
{lang('BoostingDate')}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{formatDateTimeToString(info.date * 1000, lang.code, true)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<span className={styles.centered}>
|
||||||
|
{renderText(
|
||||||
|
info.usedAt ? lang('BoostingUsedLinkDate', formatDateTimeToString(info.usedAt * 1000, lang.code, true))
|
||||||
|
: lang('BoostingSendLinkToAnyone'),
|
||||||
|
['simple_markdown'],
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<Button onClick={handleButtonClick}>
|
||||||
|
{canUse ? lang('BoostingUseLink') : lang('Close')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
hasCloseButton
|
||||||
|
isSlim
|
||||||
|
title={lang('lng_gift_link_title')}
|
||||||
|
contentClassName={styles.content}
|
||||||
|
onClose={closeGiftCodeModal}
|
||||||
|
>
|
||||||
|
{renderContent()}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(GiftCodeModal);
|
||||||
@ -22,7 +22,7 @@ import useInterval from '../../../hooks/useInterval';
|
|||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
|
|
||||||
import AnimatedIcon from '../../common/AnimatedIcon';
|
import AnimatedIcon from '../../common/AnimatedIcon';
|
||||||
import InviteLink from '../../common/InviteLink';
|
import LinkField from '../../common/LinkField';
|
||||||
import NothingFound from '../../common/NothingFound';
|
import NothingFound from '../../common/NothingFound';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
@ -283,9 +283,10 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
|||||||
<p className="text-muted">{isChannel ? lang('PrimaryLinkHelpChannel') : lang('PrimaryLinkHelp')}</p>
|
<p className="text-muted">{isChannel ? lang('PrimaryLinkHelpChannel') : lang('PrimaryLinkHelp')}</p>
|
||||||
</div>
|
</div>
|
||||||
{primaryInviteLink && (
|
{primaryInviteLink && (
|
||||||
<InviteLink
|
<LinkField
|
||||||
className="section"
|
className="section"
|
||||||
inviteLink={primaryInviteLink}
|
link={primaryInviteLink}
|
||||||
|
withShare
|
||||||
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')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import useLang from '../../../hooks/useLang';
|
|||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import Icon from '../../common/Icon';
|
import Icon from '../../common/Icon';
|
||||||
import InviteLink from '../../common/InviteLink';
|
import LinkField from '../../common/LinkField';
|
||||||
import PremiumProgress from '../../common/PremiumProgress';
|
import PremiumProgress from '../../common/PremiumProgress';
|
||||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
@ -135,7 +135,7 @@ const BoostStatistics = ({
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<InviteLink className={styles.section} inviteLink={status!.boostUrl} title={lang('LinkForBoosting')} />
|
<LinkField className={styles.section} link={status!.boostUrl} withShare title={lang('LinkForBoosting')} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -237,6 +237,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.adaptive {
|
||||||
|
--ripple-color: var(--accent-background-active-color);
|
||||||
|
background-color: var(--accent-background-color);
|
||||||
|
color: var(--accent-color);
|
||||||
|
|
||||||
|
@include active-styles() {
|
||||||
|
background-color: var(--accent-background-active-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include no-ripple-styles() {
|
||||||
|
background-color: var(--accent-background-active-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.dark {
|
&.dark {
|
||||||
background-color: rgba(0, 0, 0, 0.75);
|
background-color: rgba(0, 0, 0, 0.75);
|
||||||
color: white;
|
color: white;
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export type OwnProps = {
|
|||||||
size?: 'default' | 'smaller' | 'tiny';
|
size?: 'default' | 'smaller' | 'tiny';
|
||||||
color?: (
|
color?: (
|
||||||
'primary' | 'secondary' | 'gray' | 'danger' | 'translucent' | 'translucent-white' | 'translucent-black'
|
'primary' | 'secondary' | 'gray' | 'danger' | 'translucent' | 'translucent-white' | 'translucent-black'
|
||||||
| 'translucent-bordered' | 'dark' | 'green'
|
| 'translucent-bordered' | 'dark' | 'green' | 'adaptive'
|
||||||
);
|
);
|
||||||
backgroundImage?: string;
|
backgroundImage?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export const CUSTOM_EMOJI_PREVIEW_CACHE_DISABLED = false;
|
|||||||
export const CUSTOM_EMOJI_PREVIEW_CACHE_NAME = 'tt-custom-emoji-preview';
|
export const CUSTOM_EMOJI_PREVIEW_CACHE_NAME = 'tt-custom-emoji-preview';
|
||||||
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
|
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
|
||||||
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
|
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
|
||||||
export const LANG_CACHE_NAME = 'tt-lang-packs-v25';
|
export const LANG_CACHE_NAME = 'tt-lang-packs-v26';
|
||||||
export const ASSET_CACHE_NAME = 'tt-assets';
|
export const ASSET_CACHE_NAME = 'tt-assets';
|
||||||
export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500];
|
export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500];
|
||||||
export const DATA_BROADCAST_CHANNEL_NAME = 'tt-global';
|
export const DATA_BROADCAST_CHANNEL_NAME = 'tt-global';
|
||||||
|
|||||||
@ -975,6 +975,7 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp
|
|||||||
openChatByUsername: openChatByUsernameAction,
|
openChatByUsername: openChatByUsernameAction,
|
||||||
openStoryViewerByUsername,
|
openStoryViewerByUsername,
|
||||||
processBoostParameters,
|
processBoostParameters,
|
||||||
|
checkGiftCode,
|
||||||
} = actions;
|
} = actions;
|
||||||
|
|
||||||
if (url.match(RE_TG_LINK)) {
|
if (url.match(RE_TG_LINK)) {
|
||||||
@ -1057,6 +1058,12 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (part1 === 'giftcode') {
|
||||||
|
const slug = part2;
|
||||||
|
checkGiftCode({ slug, tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatOrChannelPostId = part2 || undefined;
|
const chatOrChannelPostId = part2 || undefined;
|
||||||
const messageId = part3 ? Number(part3) : undefined;
|
const messageId = part3 ? Number(part3) : undefined;
|
||||||
const commentId = params.comment ? Number(params.comment) : undefined;
|
const commentId = params.comment ? Number(params.comment) : undefined;
|
||||||
|
|||||||
@ -5,12 +5,14 @@ import { PaymentStep } from '../../../types';
|
|||||||
|
|
||||||
import { DEBUG_PAYMENT_SMART_GLOCAL } from '../../../config';
|
import { DEBUG_PAYMENT_SMART_GLOCAL } from '../../../config';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
import { buildCollectionByKey, unique } from '../../../util/iteratees';
|
||||||
|
import * as langProvider from '../../../util/langProvider';
|
||||||
import { buildQueryString } from '../../../util/requestQuery';
|
import { buildQueryString } from '../../../util/requestQuery';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import { getStripeError } from '../../helpers';
|
import { getStripeError, isChatChannel } from '../../helpers';
|
||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
import {
|
import {
|
||||||
|
addChats,
|
||||||
addUsers, closeInvoice,
|
addUsers, closeInvoice,
|
||||||
setInvoiceInfo, setPaymentForm,
|
setInvoiceInfo, setPaymentForm,
|
||||||
setPaymentStep,
|
setPaymentStep,
|
||||||
@ -459,3 +461,213 @@ async function validateRequestedInfo<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addActionHandler('openBoostModal', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { chatId, tabId = getCurrentTabId() } = payload;
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
if (!chat || !isChatChannel(chat)) return;
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostModal: {
|
||||||
|
chatId,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
const result = await callApi('fetchBoostsStatus', {
|
||||||
|
chat,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
actions.closeBoostModal({ tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostModal: {
|
||||||
|
chatId,
|
||||||
|
boostStatus: result,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
const myBoosts = await callApi('fetchMyBoosts');
|
||||||
|
|
||||||
|
if (!myBoosts) return;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
if (!tabState.boostModal) return;
|
||||||
|
|
||||||
|
global = addChats(global, buildCollectionByKey(myBoosts.chats, 'id'));
|
||||||
|
global = addUsers(global, buildCollectionByKey(myBoosts.users, 'id'));
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostModal: {
|
||||||
|
...tabState.boostModal,
|
||||||
|
myBoosts: myBoosts.boosts,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
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 [boostsListResult, boostStatusResult] = await Promise.all([
|
||||||
|
callApi('fetchBoostsList', { chat }),
|
||||||
|
callApi('fetchBoostsStatus', { chat }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
if (!boostsListResult || !boostStatusResult) {
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostStatistics: undefined,
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = addUsers(global, buildCollectionByKey(boostsListResult.users, 'id'));
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostStatistics: {
|
||||||
|
chatId,
|
||||||
|
boostStatus: boostStatusResult,
|
||||||
|
boosters: boostsListResult.boosters,
|
||||||
|
boosterIds: boostsListResult.boosterIds,
|
||||||
|
count: boostsListResult.count,
|
||||||
|
nextOffset: boostsListResult.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('fetchBoostsList', {
|
||||||
|
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> => {
|
||||||
|
const { chatId, slots, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
if (!chat) return;
|
||||||
|
|
||||||
|
const result = await callApi('applyBoost', {
|
||||||
|
slots,
|
||||||
|
chat,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newStatusResult = await callApi('fetchBoostsStatus', {
|
||||||
|
chat,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!newStatusResult) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
if (!tabState.boostModal?.boostStatus) return;
|
||||||
|
global = updateTabState(global, {
|
||||||
|
boostModal: {
|
||||||
|
...tabState.boostModal,
|
||||||
|
boostStatus: newStatusResult,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('checkGiftCode', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { slug, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
const result = await callApi('checkGiftCode', {
|
||||||
|
slug,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
actions.showNotification({
|
||||||
|
message: langProvider.translate('lng_gift_link_expired'),
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||||
|
global = addChats(global, buildCollectionByKey(result.chats, 'id'));
|
||||||
|
global = updateTabState(global, {
|
||||||
|
giftCodeModal: {
|
||||||
|
slug,
|
||||||
|
info: result.code,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('applyGiftCode', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { slug, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
const result = await callApi('applyGiftCode', {
|
||||||
|
slug,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
actions.requestConfetti({ tabId });
|
||||||
|
actions.closeGiftCodeModal({ tabId });
|
||||||
|
});
|
||||||
|
|||||||
@ -2,11 +2,11 @@ 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, unique } from '../../../util/iteratees';
|
import { buildCollectionByKey } 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';
|
||||||
import { buildApiInputPrivacyRules, isChatChannel } from '../../helpers';
|
import { buildApiInputPrivacyRules } from '../../helpers';
|
||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
import {
|
import {
|
||||||
addChats,
|
addChats,
|
||||||
@ -26,10 +26,8 @@ import {
|
|||||||
updateStoryViews,
|
updateStoryViews,
|
||||||
updateStoryViewsLoading,
|
updateStoryViewsLoading,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
|
||||||
import {
|
import {
|
||||||
selectChat,
|
selectPeer, selectPeerStories, selectPeerStory,
|
||||||
selectPeer, selectPeerStories, selectPeerStory, selectTabState,
|
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
|
||||||
const INFINITE_LOOP_MARKER = 100;
|
const INFINITE_LOOP_MARKER = 100;
|
||||||
@ -504,172 +502,3 @@ addActionHandler('activateStealthMode', (global, actions, payload): ActionReturn
|
|||||||
|
|
||||||
callApi('activateStealthMode', { isForPast: isForPast || true, isForFuture: isForFuture || true });
|
callApi('activateStealthMode', { isForPast: isForPast || true, isForFuture: isForFuture || true });
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('openBoostModal', async (global, actions, payload): Promise<void> => {
|
|
||||||
const { chatId, tabId = getCurrentTabId() } = payload;
|
|
||||||
const chat = selectChat(global, chatId);
|
|
||||||
if (!chat || !isChatChannel(chat)) return;
|
|
||||||
|
|
||||||
global = updateTabState(global, {
|
|
||||||
boostModal: {
|
|
||||||
chatId,
|
|
||||||
},
|
|
||||||
}, tabId);
|
|
||||||
setGlobal(global);
|
|
||||||
|
|
||||||
const result = await callApi('fetchBoostsStatus', {
|
|
||||||
chat,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
actions.closeBoostModal({ tabId });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
global = getGlobal();
|
|
||||||
global = updateTabState(global, {
|
|
||||||
boostModal: {
|
|
||||||
chatId,
|
|
||||||
boostStatus: result,
|
|
||||||
},
|
|
||||||
}, tabId);
|
|
||||||
setGlobal(global);
|
|
||||||
|
|
||||||
const myBoosts = await callApi('fetchMyBoosts');
|
|
||||||
|
|
||||||
if (!myBoosts) return;
|
|
||||||
|
|
||||||
global = getGlobal();
|
|
||||||
const tabState = selectTabState(global, tabId);
|
|
||||||
if (!tabState.boostModal) return;
|
|
||||||
|
|
||||||
global = addChats(global, buildCollectionByKey(myBoosts.chats, 'id'));
|
|
||||||
global = addUsers(global, buildCollectionByKey(myBoosts.users, 'id'));
|
|
||||||
global = updateTabState(global, {
|
|
||||||
boostModal: {
|
|
||||||
...tabState.boostModal,
|
|
||||||
myBoosts: myBoosts.boosts,
|
|
||||||
},
|
|
||||||
}, tabId);
|
|
||||||
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> => {
|
|
||||||
const { chatId, slots, tabId = getCurrentTabId() } = payload;
|
|
||||||
|
|
||||||
const chat = selectChat(global, chatId);
|
|
||||||
if (!chat) return;
|
|
||||||
|
|
||||||
const result = await callApi('applyBoost', {
|
|
||||||
slots,
|
|
||||||
chat,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newStatusResult = await callApi('fetchBoostsStatus', {
|
|
||||||
chat,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!newStatusResult) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
global = getGlobal();
|
|
||||||
const tabState = selectTabState(global, tabId);
|
|
||||||
if (!tabState.boostModal?.boostStatus) return;
|
|
||||||
global = updateTabState(global, {
|
|
||||||
boostModal: {
|
|
||||||
...tabState.boostModal,
|
|
||||||
boostStatus: newStatusResult,
|
|
||||||
},
|
|
||||||
}, tabId);
|
|
||||||
setGlobal(global);
|
|
||||||
});
|
|
||||||
|
|||||||
@ -455,17 +455,15 @@ addActionHandler('closeGame', (global, actions, payload): ActionReturnType => {
|
|||||||
|
|
||||||
addActionHandler('requestConfetti', (global, actions, payload): ActionReturnType => {
|
addActionHandler('requestConfetti', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
top, left, width, height, tabId = getCurrentTabId(),
|
tabId = getCurrentTabId(), ...rest
|
||||||
} = payload || {};
|
} = payload;
|
||||||
|
|
||||||
if (!selectCanAnimateInterface(global)) return undefined;
|
if (!selectCanAnimateInterface(global)) return undefined;
|
||||||
|
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
confetti: {
|
confetti: {
|
||||||
lastConfettiTime: Date.now(),
|
lastConfettiTime: Date.now(),
|
||||||
top,
|
...rest,
|
||||||
left,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -31,3 +31,11 @@ addActionHandler('addPaymentError', (global, actions, payload): ActionReturnType
|
|||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeGiftCodeModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
giftCodeModal: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|||||||
@ -120,6 +120,7 @@ export function getMessageSummaryDescription(
|
|||||||
location,
|
location,
|
||||||
game,
|
game,
|
||||||
storyData,
|
storyData,
|
||||||
|
giveaway,
|
||||||
} = message.content;
|
} = message.content;
|
||||||
|
|
||||||
let hasUsedTruncatedText = false;
|
let hasUsedTruncatedText = false;
|
||||||
@ -190,6 +191,10 @@ export function getMessageSummaryDescription(
|
|||||||
summary = `🎮 ${game.title}`;
|
summary = `🎮 ${game.title}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (giveaway) {
|
||||||
|
summary = lang('BoostingGiveawayChannelStarted');
|
||||||
|
}
|
||||||
|
|
||||||
if (storyData) {
|
if (storyData) {
|
||||||
if (storyData.isMention) {
|
if (storyData.isMention) {
|
||||||
// eslint-disable-next-line eslint-multitab-tt/no-immediate-global
|
// eslint-disable-next-line eslint-multitab-tt/no-immediate-global
|
||||||
|
|||||||
@ -55,12 +55,12 @@ export function getMessageTranscription(message: ApiMessage) {
|
|||||||
export function hasMessageText(message: ApiMessage | ApiStory) {
|
export function hasMessageText(message: ApiMessage | ApiStory) {
|
||||||
const {
|
const {
|
||||||
text, sticker, photo, video, audio, voice, document, poll, webPage, contact, invoice, location,
|
text, sticker, photo, video, audio, voice, document, poll, webPage, contact, invoice, location,
|
||||||
game, action, storyData,
|
game, action, storyData, giveaway,
|
||||||
} = message.content;
|
} = message.content;
|
||||||
|
|
||||||
return Boolean(text) || !(
|
return Boolean(text) || !(
|
||||||
sticker || photo || video || audio || voice || document || contact || poll || webPage || invoice || location
|
sticker || photo || video || audio || voice || document || contact || poll || webPage || invoice || location
|
||||||
|| game || action?.phoneCall || storyData
|
|| game || action?.phoneCall || storyData || giveaway
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -569,7 +569,7 @@ export function selectAllowedMessageActions<T extends GlobalState>(global: T, me
|
|||||||
|| getServerTime() - message.date < MESSAGE_EDIT_ALLOWED_TIME
|
|| getServerTime() - message.date < MESSAGE_EDIT_ALLOWED_TIME
|
||||||
) && !(
|
) && !(
|
||||||
content.sticker || content.contact || content.poll || content.action || content.audio
|
content.sticker || content.contact || content.poll || content.action || content.audio
|
||||||
|| (content.video?.isRound) || content.location || content.invoice
|
|| (content.video?.isRound) || content.location || content.invoice || content.giveaway
|
||||||
)
|
)
|
||||||
&& !isForwarded
|
&& !isForwarded
|
||||||
&& !message.viaBotId
|
&& !message.viaBotId
|
||||||
|
|||||||
@ -6,6 +6,15 @@ import { getCurrentTabId } from '../../util/establishMultitabRole';
|
|||||||
import { selectTabState } from './tabs';
|
import { selectTabState } from './tabs';
|
||||||
import { selectIsCurrentUserPremium } from './users';
|
import { selectIsCurrentUserPremium } from './users';
|
||||||
|
|
||||||
|
// https://github.com/DrKLO/Telegram/blob/c319639e9a4dff2f22da6762dcebd12d49f5afa1/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/boosts/cells/msg/GiveawayMessageCell.java#L59
|
||||||
|
const MONTH_EMOTICON: Record<number, string> = {
|
||||||
|
1: `${1}\u{FE0F}\u20E3`,
|
||||||
|
3: `${2}\u{FE0F}\u20E3`,
|
||||||
|
6: `${3}\u{FE0F}\u20E3`,
|
||||||
|
12: `${4}\u{FE0F}\u20E3`,
|
||||||
|
24: `${5}\u{FE0F}\u20E3`,
|
||||||
|
};
|
||||||
|
|
||||||
export function selectIsStickerFavorite<T extends GlobalState>(global: T, sticker: ApiSticker) {
|
export function selectIsStickerFavorite<T extends GlobalState>(global: T, sticker: ApiSticker) {
|
||||||
const { stickers } = global.stickers.favorite;
|
const { stickers } = global.stickers.favorite;
|
||||||
return stickers && stickers.some(({ id }) => id === sticker.id);
|
return stickers && stickers.some(({ id }) => id === sticker.id);
|
||||||
@ -140,3 +149,10 @@ export function selectIsAlwaysHighPriorityEmoji<T extends GlobalState>(
|
|||||||
return stickerSet.id === global.appConfig?.defaultEmojiStatusesStickerSetId
|
return stickerSet.id === global.appConfig?.defaultEmojiStatusesStickerSetId
|
||||||
|| stickerSet.id === RESTRICTED_EMOJI_SET_ID;
|
|| stickerSet.id === RESTRICTED_EMOJI_SET_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectGiftStickerForDuration<T extends GlobalState>(global: T, duration = 1) {
|
||||||
|
const stickers = global.premiumGifts?.stickers;
|
||||||
|
if (!stickers) return undefined;
|
||||||
|
const emoji = MONTH_EMOTICON[duration];
|
||||||
|
return stickers.find((sticker) => sticker.emoji === emoji) || stickers[0];
|
||||||
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import type {
|
|||||||
ApiChatlistInvite,
|
ApiChatlistInvite,
|
||||||
ApiChatReactions,
|
ApiChatReactions,
|
||||||
ApiChatType,
|
ApiChatType,
|
||||||
|
ApiCheckedGiftCode,
|
||||||
ApiConfig,
|
ApiConfig,
|
||||||
ApiContact,
|
ApiContact,
|
||||||
ApiCountry,
|
ApiCountry,
|
||||||
@ -634,6 +635,11 @@ export type TabState = {
|
|||||||
nextOffset?: string;
|
nextOffset?: string;
|
||||||
count?: number;
|
count?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
giftCodeModal?: {
|
||||||
|
slug: string;
|
||||||
|
info: ApiCheckedGiftCode;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GlobalState = {
|
export type GlobalState = {
|
||||||
@ -1805,6 +1811,14 @@ export interface ActionPayloads {
|
|||||||
isEnabled: boolean;
|
isEnabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
checkGiftCode: {
|
||||||
|
slug: string;
|
||||||
|
} & WithTabId;
|
||||||
|
applyGiftCode: {
|
||||||
|
slug: string;
|
||||||
|
} & WithTabId;
|
||||||
|
closeGiftCodeModal: WithTabId | undefined;
|
||||||
|
|
||||||
checkChatlistInvite: {
|
checkChatlistInvite: {
|
||||||
slug: string;
|
slug: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
@ -2522,7 +2536,7 @@ export interface ActionPayloads {
|
|||||||
left: number;
|
left: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
} & WithTabId) | undefined;
|
} & WithTabId) | WithTabId;
|
||||||
|
|
||||||
updateAttachmentSettings: {
|
updateAttachmentSettings: {
|
||||||
shouldCompress?: boolean;
|
shouldCompress?: boolean;
|
||||||
|
|||||||
@ -1433,6 +1433,9 @@ payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.Payment
|
|||||||
payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
|
payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
|
||||||
payments.sendPaymentForm#2d03522f flags:# form_id:long invoice:InputInvoice requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials tip_amount:flags.2?long = payments.PaymentResult;
|
payments.sendPaymentForm#2d03522f flags:# form_id:long invoice:InputInvoice requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials tip_amount:flags.2?long = payments.PaymentResult;
|
||||||
payments.getSavedInfo#227d824b = payments.SavedInfo;
|
payments.getSavedInfo#227d824b = payments.SavedInfo;
|
||||||
|
payments.checkGiftCode#8e51b4c1 slug:string = payments.CheckedGiftCode;
|
||||||
|
payments.applyGiftCode#f6e26854 slug:string = Updates;
|
||||||
|
payments.getGiveawayInfo#f4239425 peer:InputPeer msg_id:int = payments.GiveawayInfo;
|
||||||
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||||
phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||||
phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
|
phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||||
|
|||||||
@ -318,5 +318,8 @@
|
|||||||
"premium.getBoostsStatus",
|
"premium.getBoostsStatus",
|
||||||
"premium.getBoostersList",
|
"premium.getBoostersList",
|
||||||
"premium.applyBoost",
|
"premium.applyBoost",
|
||||||
"premium.getMyBoosts"
|
"premium.getMyBoosts",
|
||||||
|
"payments.checkGiftCode",
|
||||||
|
"payments.applyGiftCode",
|
||||||
|
"payments.getGiveawayInfo"
|
||||||
]
|
]
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { IS_SAFARI } from './windowEnvironment';
|
|||||||
|
|
||||||
type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'addemoji' |
|
type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'addemoji' |
|
||||||
'setlanguage' | 'addtheme' | 'confirmphone' | 'socks' | 'proxy' | 'privatepost' | 'bg' | 'share' | 'msg' | 'msg_url' |
|
'setlanguage' | 'addtheme' | 'confirmphone' | 'socks' | 'proxy' | 'privatepost' | 'bg' | 'share' | 'msg' | 'msg_url' |
|
||||||
'invoice' | 'addlist' | 'boost';
|
'invoice' | 'addlist' | 'boost' | 'giftcode';
|
||||||
|
|
||||||
export const processDeepLink = (url: string) => {
|
export const processDeepLink = (url: string) => {
|
||||||
const {
|
const {
|
||||||
@ -29,6 +29,7 @@ export const processDeepLink = (url: string) => {
|
|||||||
checkChatlistInvite,
|
checkChatlistInvite,
|
||||||
openStoryViewerByUsername,
|
openStoryViewerByUsername,
|
||||||
processBoostParameters,
|
processBoostParameters,
|
||||||
|
checkGiftCode,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
// Safari thinks the path in tg://path links is hostname for some reason
|
// Safari thinks the path in tg://path links is hostname for some reason
|
||||||
@ -157,6 +158,12 @@ export const processDeepLink = (url: string) => {
|
|||||||
processBoostParameters({ usernameOrId: channel || domain, isPrivate });
|
processBoostParameters({ usernameOrId: channel || domain, isPrivate });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'giftcode': {
|
||||||
|
const { slug } = params;
|
||||||
|
checkGiftCode({ slug });
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// Unsupported deeplink
|
// Unsupported deeplink
|
||||||
|
|
||||||
|
|||||||
@ -115,6 +115,11 @@ export function uncompressEmoji(data: EmojiRawData): EmojiData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isoToEmoji(iso: string) {
|
export function isoToEmoji(iso: string) {
|
||||||
|
// Special case for Fragment numbers
|
||||||
|
if (iso === 'FT') {
|
||||||
|
return '\uD83C\uDFF4\u200D\u2620\uFE0F';
|
||||||
|
}
|
||||||
|
|
||||||
const code = iso.toUpperCase();
|
const code = iso.toUpperCase();
|
||||||
|
|
||||||
if (!/^[A-Z]{2}$/.test(code)) return iso;
|
if (!/^[A-Z]{2}$/.test(code)) return iso;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user