Layer 181: Telegram Stars, Collapsible quotes, Fact Check (#4637)
This commit is contained in:
parent
9034a66c9c
commit
46c85ebb88
@ -260,6 +260,15 @@ export function buildApiMessageEntity(entity: GramJs.TypeMessageEntity): ApiMess
|
||||
};
|
||||
}
|
||||
|
||||
if (entity instanceof GramJs.MessageEntityBlockquote) {
|
||||
return {
|
||||
type: ApiMessageEntityTypes.Blockquote,
|
||||
canCollapse: entity.collapsed,
|
||||
offset,
|
||||
length,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: type as `${ApiMessageEntityDefault['type']}`,
|
||||
offset,
|
||||
|
||||
@ -6,6 +6,7 @@ import type {
|
||||
ApiAttachment,
|
||||
ApiChat,
|
||||
ApiContact,
|
||||
ApiFactCheck,
|
||||
ApiGroupCall,
|
||||
ApiInputMessageReplyInfo,
|
||||
ApiInputReplyInfo,
|
||||
@ -52,6 +53,7 @@ import {
|
||||
} from '../helpers';
|
||||
import { buildApiCallDiscardReason } from './calls';
|
||||
import {
|
||||
buildApiFormattedText,
|
||||
buildApiPhoto,
|
||||
} from './common';
|
||||
import { buildMessageContent, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
|
||||
@ -149,12 +151,7 @@ export function buildApiMessageFromNotification(
|
||||
|
||||
export type UniversalMessage = (
|
||||
Pick<GramJs.Message & GramJs.MessageService, ('id' | 'date')>
|
||||
& Pick<Partial<GramJs.Message & GramJs.MessageService>, (
|
||||
'out' | 'message' | 'entities' | 'fromId' | 'peerId' | 'fwdFrom' | 'replyTo' | 'replyMarkup' | 'post' |
|
||||
'media' | 'action' | 'views' | 'editDate' | 'editHide' | 'mediaUnread' | 'groupedId' | 'mentioned' | 'viaBotId' |
|
||||
'replies' | 'fromScheduled' | 'postAuthor' | 'noforwards' | 'reactions' | 'forwards' | 'silent' | 'pinned' |
|
||||
'savedPeerId' | 'fromBoostsApplied' | 'quickReplyShortcutId' | 'viaBusinessBotId'
|
||||
)>
|
||||
& Partial<GramJs.Message & GramJs.MessageService>
|
||||
);
|
||||
|
||||
export function buildApiMessageWithChatId(
|
||||
@ -192,6 +189,7 @@ export function buildApiMessageWithChatId(
|
||||
const emojiOnlyCount = getEmojiOnlyCountForMessage(content, groupedId);
|
||||
const hasComments = mtpMessage.replies?.comments;
|
||||
const senderBoosts = mtpMessage.fromBoostsApplied;
|
||||
const factCheck = mtpMessage.factcheck && buildApiFactCheck(mtpMessage.factcheck);
|
||||
|
||||
const savedPeerId = mtpMessage.savedPeerId && getApiChatIdFromMtpPeer(mtpMessage.savedPeerId);
|
||||
|
||||
@ -234,6 +232,7 @@ export function buildApiMessageWithChatId(
|
||||
savedPeerId,
|
||||
senderBoosts,
|
||||
viaBusinessBotId: mtpMessage.viaBusinessBotId?.toString(),
|
||||
factCheck,
|
||||
});
|
||||
}
|
||||
|
||||
@ -319,6 +318,15 @@ function buildApiReplyInfo(replyHeader: GramJs.TypeMessageReplyHeader): ApiReply
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function buildApiFactCheck(factCheck: GramJs.FactCheck): ApiFactCheck {
|
||||
return {
|
||||
shouldFetch: factCheck.needCheck,
|
||||
hash: factCheck.hash.toString(),
|
||||
text: factCheck.text && buildApiFormattedText(factCheck.text),
|
||||
countryCode: factCheck.country,
|
||||
};
|
||||
}
|
||||
|
||||
function buildAction(
|
||||
action: GramJs.TypeMessageAction,
|
||||
senderId: string | undefined,
|
||||
@ -450,6 +458,7 @@ function buildAction(
|
||||
amount = Number(action.totalAmount);
|
||||
currency = action.currency;
|
||||
text = 'PaymentSuccessfullyPaid';
|
||||
type = 'receipt';
|
||||
if (targetPeerId) {
|
||||
targetUserIds.push(targetPeerId);
|
||||
}
|
||||
|
||||
@ -9,8 +9,12 @@ import type {
|
||||
ApiInvoice, ApiLabeledPrice, ApiMyBoost, ApiPaymentCredentials,
|
||||
ApiPaymentForm, ApiPaymentSavedInfo, ApiPremiumGiftCodeOption, ApiPremiumPromo, ApiPremiumSubscriptionOption,
|
||||
ApiReceipt,
|
||||
ApiStarsTransaction,
|
||||
ApiStarsTransactionPeer,
|
||||
ApiStarTopupOption,
|
||||
} from '../../types';
|
||||
|
||||
import { addWebDocumentToLocalDb } from '../helpers';
|
||||
import { buildApiMessageEntity } from './common';
|
||||
import { omitVirtualClassFields } from './helpers';
|
||||
import { buildApiDocument, buildApiWebDocument } from './messageContent';
|
||||
@ -37,7 +41,35 @@ export function buildShippingOptions(shippingOptions: GramJs.ShippingOption[] |
|
||||
});
|
||||
}
|
||||
|
||||
export function buildApiReceipt(receipt: GramJs.payments.PaymentReceipt): ApiReceipt {
|
||||
export function buildApiReceipt(receipt: GramJs.payments.TypePaymentReceipt): ApiReceipt {
|
||||
const { photo } = receipt;
|
||||
|
||||
if (photo) {
|
||||
addWebDocumentToLocalDb(photo);
|
||||
}
|
||||
|
||||
if (receipt instanceof GramJs.payments.PaymentReceiptStars) {
|
||||
const {
|
||||
botId, currency, date, description: text, title, totalAmount, transactionId,
|
||||
} = receipt;
|
||||
|
||||
if (photo) {
|
||||
addWebDocumentToLocalDb(photo);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'stars',
|
||||
currency,
|
||||
botId: buildApiPeerId(botId, 'user'),
|
||||
date,
|
||||
text,
|
||||
title,
|
||||
totalAmount: -totalAmount.toJSNumber(),
|
||||
transactionId,
|
||||
photo: photo && buildApiWebDocument(photo),
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
invoice,
|
||||
info,
|
||||
@ -46,6 +78,8 @@ export function buildApiReceipt(receipt: GramJs.payments.PaymentReceipt): ApiRec
|
||||
totalAmount,
|
||||
credentialsTitle,
|
||||
tipAmount,
|
||||
title,
|
||||
description: text,
|
||||
} = receipt;
|
||||
|
||||
const { shippingAddress, phone, name } = (info || {});
|
||||
@ -70,6 +104,7 @@ export function buildApiReceipt(receipt: GramJs.payments.PaymentReceipt): ApiRec
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'regular',
|
||||
currency,
|
||||
prices: mappedPrices,
|
||||
info: { shippingAddress, phone, name },
|
||||
@ -78,10 +113,23 @@ export function buildApiReceipt(receipt: GramJs.payments.PaymentReceipt): ApiRec
|
||||
shippingPrices,
|
||||
shippingMethod,
|
||||
tipAmount: tipAmount ? tipAmount.toJSNumber() : 0,
|
||||
title,
|
||||
text,
|
||||
photo: photo && buildApiWebDocument(photo),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiPaymentForm(form: GramJs.payments.PaymentForm): ApiPaymentForm {
|
||||
export function buildApiPaymentForm(form: GramJs.payments.TypePaymentForm): ApiPaymentForm {
|
||||
if (form instanceof GramJs.payments.PaymentFormStars) {
|
||||
const { botId, formId } = form;
|
||||
|
||||
return {
|
||||
type: 'stars',
|
||||
botId: buildApiPeerId(botId, 'user'),
|
||||
formId: String(formId),
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
formId,
|
||||
canSaveCredentials,
|
||||
@ -93,6 +141,7 @@ export function buildApiPaymentForm(form: GramJs.payments.PaymentForm): ApiPayme
|
||||
invoice,
|
||||
savedCredentials,
|
||||
url,
|
||||
botId,
|
||||
} = form;
|
||||
|
||||
const {
|
||||
@ -121,7 +170,9 @@ export function buildApiPaymentForm(form: GramJs.payments.PaymentForm): ApiPayme
|
||||
const nativeData = nativeParams ? JSON.parse(nativeParams.data) : {};
|
||||
|
||||
return {
|
||||
type: 'regular',
|
||||
url,
|
||||
botId: buildApiPeerId(botId, 'user'),
|
||||
canSaveCredentials,
|
||||
isPasswordMissing,
|
||||
formId: String(formId),
|
||||
@ -146,12 +197,13 @@ export function buildApiPaymentForm(form: GramJs.payments.PaymentForm): ApiPayme
|
||||
needZip: Boolean(nativeData?.need_zip),
|
||||
publishableKey: nativeData?.publishable_key,
|
||||
publicToken: nativeData?.public_token,
|
||||
tokenizeUrl: nativeData?.tokenize_url,
|
||||
},
|
||||
...(savedCredentials && { savedCredentials: buildApiPaymentCredentials(savedCredentials) }),
|
||||
savedCredentials: savedCredentials && buildApiPaymentCredentials(savedCredentials),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiInvoiceFromForm(form: GramJs.payments.PaymentForm): ApiInvoice {
|
||||
export function buildApiInvoiceFromForm(form: GramJs.payments.TypePaymentForm): ApiInvoice {
|
||||
const {
|
||||
invoice, description: text, title, photo,
|
||||
} = form;
|
||||
@ -328,3 +380,61 @@ export function buildApiPremiumGiftCodeOption(option: GramJs.PremiumGiftCodeOpti
|
||||
users,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiStarsTransactionPeer(peer: GramJs.TypeStarsTransactionPeer): ApiStarsTransactionPeer {
|
||||
if (peer instanceof GramJs.StarsTransactionPeerAppStore) {
|
||||
return { type: 'appStore' };
|
||||
}
|
||||
|
||||
if (peer instanceof GramJs.StarsTransactionPeerPlayMarket) {
|
||||
return { type: 'playMarket' };
|
||||
}
|
||||
|
||||
if (peer instanceof GramJs.StarsTransactionPeerPremiumBot) {
|
||||
return { type: 'premiumBot' };
|
||||
}
|
||||
|
||||
if (peer instanceof GramJs.StarsTransactionPeerFragment) {
|
||||
return { type: 'fragment' };
|
||||
}
|
||||
|
||||
if (peer instanceof GramJs.StarsTransactionPeer) {
|
||||
return { type: 'peer', id: getApiChatIdFromMtpPeer(peer.peer) };
|
||||
}
|
||||
|
||||
return { type: 'unsupported' };
|
||||
}
|
||||
|
||||
export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction): ApiStarsTransaction {
|
||||
const {
|
||||
date, id, peer, stars, description, photo, title, refund,
|
||||
} = transaction;
|
||||
|
||||
if (photo) {
|
||||
addWebDocumentToLocalDb(photo);
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
date,
|
||||
peer: buildApiStarsTransactionPeer(peer),
|
||||
stars: stars.toJSNumber(),
|
||||
title,
|
||||
description,
|
||||
photo: photo && buildApiWebDocument(photo),
|
||||
isRefund: refund,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiStarTopupOption(option: GramJs.TypeStarsTopupOption): ApiStarTopupOption {
|
||||
const {
|
||||
amount, currency, stars, extended,
|
||||
} = option;
|
||||
|
||||
return {
|
||||
amount: amount.toJSNumber(),
|
||||
currency,
|
||||
stars: stars.toJSNumber(),
|
||||
isExtended: extended,
|
||||
};
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import type {
|
||||
ApiReportReason,
|
||||
ApiRequestInputInvoice,
|
||||
ApiSendMessageAction,
|
||||
ApiStarTopupOption,
|
||||
ApiSticker,
|
||||
ApiStory,
|
||||
ApiStorySkipped,
|
||||
@ -604,6 +605,15 @@ function buildPremiumGiftCodeOption(optionData: ApiPremiumGiftCodeOption) {
|
||||
});
|
||||
}
|
||||
|
||||
function buildInputStarsTopupOption(option: ApiStarTopupOption) {
|
||||
return new GramJs.StarsTopupOption({
|
||||
stars: BigInt(option.stars),
|
||||
amount: BigInt(option.amount),
|
||||
currency: option.currency,
|
||||
extended: option.isExtended,
|
||||
});
|
||||
}
|
||||
|
||||
export function buildInputInvoice(invoice: ApiRequestInputInvoice) {
|
||||
switch (invoice.type) {
|
||||
case 'message': {
|
||||
@ -619,6 +629,12 @@ export function buildInputInvoice(invoice: ApiRequestInputInvoice) {
|
||||
});
|
||||
}
|
||||
|
||||
case 'stars': {
|
||||
return new GramJs.InputInvoiceStars({
|
||||
option: buildInputStarsTopupOption(invoice.option),
|
||||
});
|
||||
}
|
||||
|
||||
case 'giveaway':
|
||||
default: {
|
||||
const purpose = buildInputStorePaymentPurpose(invoice.purpose);
|
||||
|
||||
@ -83,9 +83,8 @@ function addMediaToLocalDb(media: GramJs.TypeMessageMedia) {
|
||||
addPhotoToLocalDb(media.game.photo);
|
||||
}
|
||||
|
||||
if (media instanceof GramJs.MessageMediaInvoice
|
||||
&& media.photo) {
|
||||
localDb.webDocuments[String(media.photo.url)] = media.photo;
|
||||
if (media instanceof GramJs.MessageMediaInvoice && media.photo) {
|
||||
addWebDocumentToLocalDb(media.photo);
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,6 +153,10 @@ export function addEntitiesToLocalDb(entities: (GramJs.TypeUser | GramJs.TypeCha
|
||||
});
|
||||
}
|
||||
|
||||
export function addWebDocumentToLocalDb(webDocument: GramJs.TypeWebDocument) {
|
||||
localDb.webDocuments[webDocument.url] = webDocument;
|
||||
}
|
||||
|
||||
export function swapLocalInvoiceMedia(
|
||||
chatId: string, messageId: number, extendedMedia: GramJs.TypeMessageExtendedMedia,
|
||||
) {
|
||||
|
||||
@ -33,7 +33,9 @@ import {
|
||||
buildInputThemeParams,
|
||||
generateRandomBigInt,
|
||||
} from '../gramjsBuilders';
|
||||
import { addEntitiesToLocalDb, addUserToLocalDb, deserializeBytes } from '../helpers';
|
||||
import {
|
||||
addEntitiesToLocalDb, addUserToLocalDb, addWebDocumentToLocalDb, deserializeBytes,
|
||||
} from '../helpers';
|
||||
import localDb from '../localDb';
|
||||
import { invokeRequest } from './client';
|
||||
|
||||
@ -561,10 +563,6 @@ function addPhotoToLocalDb(photo: GramJs.Photo) {
|
||||
localDb.photos[String(photo.id)] = photo;
|
||||
}
|
||||
|
||||
function addWebDocumentToLocalDb(webDocument: GramJs.TypeWebDocument) {
|
||||
localDb.webDocuments[webDocument.url] = webDocument;
|
||||
}
|
||||
|
||||
export function setBotInfo({
|
||||
bot,
|
||||
langCode,
|
||||
|
||||
@ -33,7 +33,7 @@ export {
|
||||
reportMessages, sendMessageAction, fetchSeenBy, fetchSponsoredMessages, viewSponsoredMessage, fetchSendAs,
|
||||
saveDefaultSendAs, fetchUnreadReactions, readAllReactions, fetchUnreadMentions, readAllMentions, transcribeAudio,
|
||||
closePoll, fetchExtendedMedia, translateText, fetchMessageViews, fetchDiscussionMessage, clickSponsoredMessage,
|
||||
fetchOutboxReadDate, exportMessageLink, fetchQuickReplies, sendQuickReply,
|
||||
fetchOutboxReadDate, exportMessageLink, fetchQuickReplies, sendQuickReply, fetchFactChecks,
|
||||
deleteSavedHistory,
|
||||
} from './messages';
|
||||
|
||||
@ -100,7 +100,8 @@ export * from './stories';
|
||||
export {
|
||||
validateRequestedInfo, sendPaymentForm, getPaymentForm, getReceipt, fetchPremiumPromo, fetchTemporaryPaymentPassword,
|
||||
applyBoost, fetchBoostList, fetchBoostStatus, fetchGiveawayInfo, fetchMyBoosts, applyGiftCode, checkGiftCode,
|
||||
getPremiumGiftCodeOptions, launchPrepaidGiveaway,
|
||||
getPremiumGiftCodeOptions, launchPrepaidGiveaway, fetchStarsStatus, fetchStarsTopupOptions, fetchStarsTransactions,
|
||||
sendStarPaymentForm,
|
||||
} from './payments';
|
||||
|
||||
export * from './fragment';
|
||||
|
||||
@ -49,6 +49,7 @@ import { buildApiChatFromPreview, buildApiSendAsPeerId } from '../apiBuilders/ch
|
||||
import { buildApiFormattedText } from '../apiBuilders/common';
|
||||
import { buildMessageMediaContent, buildMessageTextContent, buildWebPage } from '../apiBuilders/messageContent';
|
||||
import {
|
||||
buildApiFactCheck,
|
||||
buildApiMessage,
|
||||
buildApiQuickReply,
|
||||
buildApiSponsoredMessage,
|
||||
@ -1047,6 +1048,25 @@ export async function fetchMessageViews({
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchFactChecks({
|
||||
chat, ids,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
ids: number[];
|
||||
}) {
|
||||
const chunks = split(ids, API_GENERAL_ID_LIMIT);
|
||||
const results = await Promise.all(chunks.map((chunkIds) => (
|
||||
invokeRequest(new GramJs.messages.GetFactCheck({
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
msgId: chunkIds,
|
||||
}))
|
||||
)));
|
||||
|
||||
if (!results || results.some((result) => !result)) return undefined;
|
||||
|
||||
return results.flatMap((result) => result!).map(buildApiFactCheck);
|
||||
}
|
||||
|
||||
export async function fetchDiscussionMessage({
|
||||
chat, messageId,
|
||||
}: {
|
||||
|
||||
@ -6,6 +6,7 @@ import type {
|
||||
OnApiUpdate,
|
||||
} from '../../types';
|
||||
|
||||
import { DEBUG } from '../../../config';
|
||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import {
|
||||
buildApiBoost,
|
||||
@ -18,6 +19,8 @@ import {
|
||||
buildApiPremiumGiftCodeOption,
|
||||
buildApiPremiumPromo,
|
||||
buildApiReceipt,
|
||||
buildApiStarsTransaction,
|
||||
buildApiStarTopupOption,
|
||||
buildShippingOptions,
|
||||
} from '../apiBuilders/payments';
|
||||
import { buildApiUser } from '../apiBuilders/users';
|
||||
@ -26,6 +29,7 @@ import {
|
||||
} from '../gramjsBuilders';
|
||||
import {
|
||||
addEntitiesToLocalDb,
|
||||
addWebDocumentToLocalDb,
|
||||
deserializeBytes,
|
||||
serializeBytes,
|
||||
} from '../helpers';
|
||||
@ -124,6 +128,34 @@ export async function sendPaymentForm({
|
||||
return Boolean(result);
|
||||
}
|
||||
|
||||
export async function sendStarPaymentForm({
|
||||
formId,
|
||||
inputInvoice,
|
||||
}: {
|
||||
formId: string;
|
||||
inputInvoice: ApiRequestInputInvoice;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.SendStarsForm({
|
||||
formId: BigInt(formId),
|
||||
invoice: buildInputInvoice(inputInvoice),
|
||||
}));
|
||||
|
||||
if (!result) return false;
|
||||
|
||||
if (result instanceof GramJs.payments.PaymentVerificationNeeded) {
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Unexpected PaymentVerificationNeeded in sendStarsForm');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
} else {
|
||||
handleGramJsUpdate(result.updates);
|
||||
}
|
||||
|
||||
return Boolean(result);
|
||||
}
|
||||
|
||||
export async function getPaymentForm(inputInvoice: ApiRequestInputInvoice) {
|
||||
const result = await invokeRequest(new GramJs.payments.GetPaymentForm({
|
||||
invoice: buildInputInvoice(inputInvoice),
|
||||
@ -134,7 +166,7 @@ export async function getPaymentForm(inputInvoice: ApiRequestInputInvoice) {
|
||||
}
|
||||
|
||||
if (result.photo) {
|
||||
localDb.webDocuments[result.photo.url] = result.photo;
|
||||
addWebDocumentToLocalDb(result.photo);
|
||||
}
|
||||
|
||||
addEntitiesToLocalDb(result.users);
|
||||
@ -143,7 +175,6 @@ export async function getPaymentForm(inputInvoice: ApiRequestInputInvoice) {
|
||||
form: buildApiPaymentForm(result),
|
||||
invoice: buildApiInvoiceFromForm(result),
|
||||
users: result.users.map(buildApiUser).filter(Boolean),
|
||||
botId: result.botId.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -387,3 +418,66 @@ export function launchPrepaidGiveaway({
|
||||
shouldReturnTrue: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchStarsStatus() {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsStatus({
|
||||
peer: new GramJs.InputPeerSelf(),
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const users = result.users.map(buildApiUser).filter(Boolean);
|
||||
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
||||
|
||||
return {
|
||||
users,
|
||||
chats,
|
||||
nextOffset: result.nextOffset,
|
||||
history: result.history.map(buildApiStarsTransaction),
|
||||
balance: result.balance.toJSNumber(),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchStarsTransactions({
|
||||
offset,
|
||||
isInbound,
|
||||
isOutbound,
|
||||
}: {
|
||||
offset?: string;
|
||||
isInbound?: true;
|
||||
isOutbound?: true;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsTransactions({
|
||||
peer: new GramJs.InputPeerSelf(),
|
||||
offset,
|
||||
inbound: isInbound,
|
||||
outbound: isOutbound,
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const users = result.users.map(buildApiUser).filter(Boolean);
|
||||
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
||||
|
||||
return {
|
||||
users,
|
||||
chats,
|
||||
nextOffset: result.nextOffset,
|
||||
history: result.history.map(buildApiStarsTransaction),
|
||||
balance: result.balance.toJSNumber(),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchStarsTopupOptions() {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsTopupOptions());
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result.map(buildApiStarTopupOption);
|
||||
}
|
||||
|
||||
@ -244,12 +244,7 @@ export function updater(update: Update) {
|
||||
if (update.message instanceof GramJs.MessageService) {
|
||||
const { action } = update.message;
|
||||
|
||||
if (action instanceof GramJs.MessageActionPaymentSent) {
|
||||
onUpdate({
|
||||
'@type': 'updatePaymentStateCompleted',
|
||||
slug: action.invoiceSlug,
|
||||
});
|
||||
} else if (action instanceof GramJs.MessageActionChatEditTitle) {
|
||||
if (action instanceof GramJs.MessageActionChatEditTitle) {
|
||||
onUpdate({
|
||||
'@type': 'updateChat',
|
||||
id: message.chatId,
|
||||
@ -1194,6 +1189,11 @@ export function updater(update: Update) {
|
||||
chatId: buildApiPeerId(update.channelId, 'channel'),
|
||||
isEnabled: update.enabled ? true : undefined,
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdateStarsBalance) {
|
||||
onUpdate({
|
||||
'@type': 'updateStarsBalance',
|
||||
balance: update.balance.toJSNumber(),
|
||||
});
|
||||
} else if (update instanceof LocalUpdatePremiumFloodWait) {
|
||||
onUpdate({
|
||||
'@type': 'updatePremiumFloodWait',
|
||||
|
||||
@ -2,7 +2,7 @@ import type { ThreadId } from '../../types';
|
||||
import type { ApiWebDocument } from './bots';
|
||||
import type { ApiGroupCall, PhoneCallAction } from './calls';
|
||||
import type { ApiChat, ApiPeerColor } from './chats';
|
||||
import type { ApiInputStorePaymentPurpose, ApiPremiumGiftCodeOption } from './payments';
|
||||
import type { ApiInputStorePaymentPurpose, ApiPremiumGiftCodeOption, ApiStarTopupOption } from './payments';
|
||||
import type { ApiMessageStoryData, ApiWebPageStickerData, ApiWebPageStoryData } from './stories';
|
||||
|
||||
export interface ApiDimensions {
|
||||
@ -208,8 +208,13 @@ export type ApiInputInvoiceGiftCode = {
|
||||
option: ApiPremiumGiftCodeOption;
|
||||
};
|
||||
|
||||
export type ApiInputInvoiceStars = {
|
||||
type: 'stars';
|
||||
option: ApiStarTopupOption;
|
||||
};
|
||||
|
||||
export type ApiInputInvoice = ApiInputInvoiceMessage | ApiInputInvoiceSlug | ApiInputInvoiceGiveaway
|
||||
| ApiInputInvoiceGiftCode;
|
||||
| ApiInputInvoiceGiftCode | ApiInputInvoiceStars;
|
||||
|
||||
/* Used for Invoice request */
|
||||
export type ApiRequestInputInvoiceMessage = {
|
||||
@ -229,8 +234,13 @@ export type ApiRequestInputInvoiceGiveaway = {
|
||||
option: ApiPremiumGiftCodeOption;
|
||||
};
|
||||
|
||||
export type ApiRequestInputInvoiceStars = {
|
||||
type: 'stars';
|
||||
option: ApiStarTopupOption;
|
||||
};
|
||||
|
||||
export type ApiRequestInputInvoice = ApiRequestInputInvoiceMessage | ApiRequestInputInvoiceSlug
|
||||
| ApiRequestInputInvoiceGiveaway;
|
||||
| ApiRequestInputInvoiceGiveaway | ApiRequestInputInvoiceStars;
|
||||
|
||||
export interface ApiInvoice {
|
||||
text: string;
|
||||
@ -345,6 +355,7 @@ export interface ApiAction {
|
||||
| 'suggestProfilePhoto'
|
||||
| 'joinedChannel'
|
||||
| 'chatBoost'
|
||||
| 'receipt'
|
||||
| 'other';
|
||||
photo?: ApiPhoto;
|
||||
amount?: number;
|
||||
@ -445,7 +456,7 @@ export type ApiMessageEntityDefault = {
|
||||
type: Exclude<
|
||||
`${ApiMessageEntityTypes}`,
|
||||
`${ApiMessageEntityTypes.Pre}` | `${ApiMessageEntityTypes.TextUrl}` | `${ApiMessageEntityTypes.MentionName}` |
|
||||
`${ApiMessageEntityTypes.CustomEmoji}`
|
||||
`${ApiMessageEntityTypes.CustomEmoji}` | `${ApiMessageEntityTypes.Blockquote}`
|
||||
>;
|
||||
offset: number;
|
||||
length: number;
|
||||
@ -472,6 +483,13 @@ export type ApiMessageEntityMentionName = {
|
||||
userId: string;
|
||||
};
|
||||
|
||||
export type ApiMessageEntityBlockquote = {
|
||||
type: ApiMessageEntityTypes.Blockquote;
|
||||
offset: number;
|
||||
length: number;
|
||||
canCollapse?: boolean;
|
||||
};
|
||||
|
||||
export type ApiMessageEntityCustomEmoji = {
|
||||
type: ApiMessageEntityTypes.CustomEmoji;
|
||||
offset: number;
|
||||
@ -480,7 +498,7 @@ export type ApiMessageEntityCustomEmoji = {
|
||||
};
|
||||
|
||||
export type ApiMessageEntity = ApiMessageEntityDefault | ApiMessageEntityPre | ApiMessageEntityTextUrl |
|
||||
ApiMessageEntityMentionName | ApiMessageEntityCustomEmoji;
|
||||
ApiMessageEntityMentionName | ApiMessageEntityCustomEmoji | ApiMessageEntityBlockquote;
|
||||
|
||||
export enum ApiMessageEntityTypes {
|
||||
Bold = 'MessageEntityBold',
|
||||
@ -587,6 +605,7 @@ export interface ApiMessage {
|
||||
readDate?: number;
|
||||
savedPeerId?: string;
|
||||
senderBoosts?: number;
|
||||
factCheck?: ApiFactCheck;
|
||||
}
|
||||
|
||||
export interface ApiReactions {
|
||||
@ -835,6 +854,13 @@ export type ApiQuickReply = {
|
||||
topMessageId: number;
|
||||
};
|
||||
|
||||
export type ApiFactCheck = {
|
||||
shouldFetch?: true;
|
||||
hash: string;
|
||||
countryCode?: string;
|
||||
text?: ApiFormattedText;
|
||||
};
|
||||
|
||||
export type ApiSponsoredMessageReportResult = {
|
||||
type: 'reported' | 'hidden' | 'premiumRequired';
|
||||
} | {
|
||||
|
||||
@ -22,8 +22,10 @@ export interface ApiPaymentSavedInfo {
|
||||
shippingAddress?: ApiShippingAddress;
|
||||
}
|
||||
|
||||
export interface ApiPaymentForm {
|
||||
export interface ApiPaymentFormRegular {
|
||||
type: 'regular';
|
||||
url: string;
|
||||
botId: string;
|
||||
canSaveCredentials?: boolean;
|
||||
isPasswordMissing?: boolean;
|
||||
formId: string;
|
||||
@ -35,12 +37,21 @@ export interface ApiPaymentForm {
|
||||
nativeParams: ApiPaymentFormNativeParams;
|
||||
}
|
||||
|
||||
export interface ApiPaymentFormStars {
|
||||
type: 'stars';
|
||||
formId: string;
|
||||
botId: string;
|
||||
}
|
||||
|
||||
export type ApiPaymentForm = ApiPaymentFormRegular | ApiPaymentFormStars;
|
||||
|
||||
export interface ApiPaymentFormNativeParams {
|
||||
needCardholderName?: boolean;
|
||||
needCountry?: boolean;
|
||||
needZip?: boolean;
|
||||
publishableKey?: string;
|
||||
publicToken?: string;
|
||||
tokenizeUrl?: string;
|
||||
}
|
||||
|
||||
export interface ApiLabeledPrice {
|
||||
@ -48,7 +59,21 @@ export interface ApiLabeledPrice {
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export interface ApiReceipt {
|
||||
export interface ApiReceiptStars {
|
||||
type: 'stars';
|
||||
botId?: string;
|
||||
peer?: ApiStarsTransactionPeer;
|
||||
date: number;
|
||||
title?: string;
|
||||
text?: string;
|
||||
photo?: ApiWebDocument;
|
||||
currency: string;
|
||||
totalAmount: number;
|
||||
transactionId: string;
|
||||
}
|
||||
|
||||
export interface ApiReceiptRegular {
|
||||
type: 'regular';
|
||||
photo?: ApiWebDocument;
|
||||
text?: string;
|
||||
title?: string;
|
||||
@ -66,6 +91,8 @@ export interface ApiReceipt {
|
||||
shippingMethod?: string;
|
||||
}
|
||||
|
||||
export type ApiReceipt = ApiReceiptRegular | ApiReceiptStars;
|
||||
|
||||
export interface ApiPremiumPromo {
|
||||
videoSections: ApiPremiumSection[];
|
||||
videos: ApiDocument[];
|
||||
@ -179,3 +206,54 @@ export interface ApiPrepaidGiveaway {
|
||||
quantity: number;
|
||||
date: number;
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerUnsupported {
|
||||
type: 'unsupported';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerAppStore {
|
||||
type: 'appStore';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerPlayMarket {
|
||||
type: 'playMarket';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerPremiumBot {
|
||||
type: 'premiumBot';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerFragment {
|
||||
type: 'fragment';
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerPeer {
|
||||
type: 'peer';
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type ApiStarsTransactionPeer =
|
||||
| ApiStarsTransactionPeerUnsupported
|
||||
| ApiStarsTransactionPeerAppStore
|
||||
| ApiStarsTransactionPeerPlayMarket
|
||||
| ApiStarsTransactionPeerPremiumBot
|
||||
| ApiStarsTransactionPeerFragment
|
||||
| ApiStarsTransactionPeerPeer;
|
||||
|
||||
export interface ApiStarsTransaction {
|
||||
id: string;
|
||||
peer: ApiStarsTransactionPeer;
|
||||
stars: number;
|
||||
isRefund?: true;
|
||||
date: number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
photo?: ApiWebDocument;
|
||||
}
|
||||
|
||||
export interface ApiStarTopupOption {
|
||||
isExtended?: true;
|
||||
stars: number;
|
||||
currency: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ import type {
|
||||
} from './chats';
|
||||
import type {
|
||||
ApiFormattedText,
|
||||
ApiInputInvoice,
|
||||
ApiMessage,
|
||||
ApiMessageExtendedMediaPreview,
|
||||
ApiPhoto,
|
||||
@ -518,7 +519,7 @@ export type ApiUpdatePaymentVerificationNeeded = {
|
||||
|
||||
export type ApiUpdatePaymentStateCompleted = {
|
||||
'@type': 'updatePaymentStateCompleted';
|
||||
slug?: string;
|
||||
inputInvoice: ApiInputInvoice;
|
||||
};
|
||||
|
||||
export type ApiUpdatePrivacy = {
|
||||
@ -738,6 +739,11 @@ export type ApiUpdatePremiumFloodWait = {
|
||||
isUpload?: boolean;
|
||||
};
|
||||
|
||||
export type ApiUpdateStarsBalance = {
|
||||
'@type': 'updateStarsBalance';
|
||||
balance: number;
|
||||
};
|
||||
|
||||
export type ApiUpdate = (
|
||||
ApiUpdateReady | ApiUpdateSession | ApiUpdateWebAuthTokenFailed | ApiUpdateRequestUserUpdate |
|
||||
ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser |
|
||||
@ -768,7 +774,7 @@ export type ApiUpdate = (
|
||||
ApiRequestReconnectApi | ApiRequestSync | ApiUpdateFetchingDifference | ApiUpdateChannelMessages |
|
||||
ApiUpdateStealthMode | ApiUpdateAttachMenuBots | ApiUpdateNewAuthorization | ApiUpdateGroupInvitePrivacyForbidden |
|
||||
ApiUpdateViewForumAsMessages | ApiUpdateSavedDialogPinned | ApiUpdatePinnedSavedDialogIds | ApiUpdateChatLastMessage |
|
||||
ApiUpdateDeleteSavedHistory | ApiUpdatePremiumFloodWait |
|
||||
ApiUpdateDeleteSavedHistory | ApiUpdatePremiumFloodWait | ApiUpdateStarsBalance |
|
||||
ApiUpdateQuickReplyMessage | ApiUpdateQuickReplies | ApiDeleteQuickReply | ApiUpdateDeleteQuickReplyMessages
|
||||
);
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 567 B After Width: | Height: | Size: 567 B |
1
src/assets/icons/StarLogo.svg
Normal file
1
src/assets/icons/StarLogo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="88" height="84" fill="none"><path fill="#fff" d="M41.307 70.48 21.729 82.472a3.49 3.49 0 0 1-5.205-3.835l3.03-11.929a14.17 14.17 0 0 1 7.6-9.282l21.358-10.255a2.834 2.834 0 0 0-1.71-5.346l-23.774 4.116c-4.592.795-9.3-.473-12.871-3.466l-7.511-6.294a3.49 3.49 0 0 1 1.969-6.153l22.947-1.796a5.16 5.16 0 0 0 4.362-3.167l8.852-21.37a3.49 3.49 0 0 1 6.448 0l8.852 21.37a5.16 5.16 0 0 0 4.362 3.167l23.073 1.806a3.49 3.49 0 0 1 1.992 6.134L67.906 51.175a5.16 5.16 0 0 0-1.668 5.13l5.41 22.474a3.49 3.49 0 0 1-5.216 3.792L46.693 70.48a5.16 5.16 0 0 0-5.386 0"/><path fill="url(#a)" d="M41.307 70.48 21.729 82.472a3.49 3.49 0 0 1-5.205-3.835l3.03-11.929a14.17 14.17 0 0 1 7.6-9.282l21.358-10.255a2.834 2.834 0 0 0-1.71-5.346l-23.774 4.116c-4.592.795-9.3-.473-12.871-3.466l-7.511-6.294a3.49 3.49 0 0 1 1.969-6.153l22.947-1.796a5.16 5.16 0 0 0 4.362-3.167l8.852-21.37a3.49 3.49 0 0 1 6.448 0l8.852 21.37a5.16 5.16 0 0 0 4.362 3.167l23.073 1.806a3.49 3.49 0 0 1 1.992 6.134L67.906 51.175a5.16 5.16 0 0 0-1.668 5.13l5.41 22.474a3.49 3.49 0 0 1-5.216 3.792L46.693 70.48a5.16 5.16 0 0 0-5.386 0"/><path fill="url(#b)" d="M41.307 70.48 21.729 82.472a3.49 3.49 0 0 1-5.205-3.835l3.03-11.929a14.17 14.17 0 0 1 7.6-9.282l21.358-10.255a2.834 2.834 0 0 0-1.71-5.346l-23.774 4.116c-4.592.795-9.3-.473-12.871-3.466l-7.511-6.294a3.49 3.49 0 0 1 1.969-6.153l22.947-1.796a5.16 5.16 0 0 0 4.362-3.167l8.852-21.37a3.49 3.49 0 0 1 6.448 0l8.852 21.37a5.16 5.16 0 0 0 4.362 3.167l23.073 1.806a3.49 3.49 0 0 1 1.992 6.134L67.906 51.175a5.16 5.16 0 0 0-1.668 5.13l5.41 22.474a3.49 3.49 0 0 1-5.216 3.792L46.693 70.48a5.16 5.16 0 0 0-5.386 0"/><path stroke="url(#c)" stroke-width="1.667" d="M41.307 70.48 21.729 82.472a3.49 3.49 0 0 1-5.205-3.835l3.03-11.929a14.17 14.17 0 0 1 7.6-9.282l21.358-10.255a2.834 2.834 0 0 0-1.71-5.346l-23.774 4.116c-4.592.795-9.3-.473-12.871-3.466l-7.511-6.294a3.49 3.49 0 0 1 1.969-6.153l22.947-1.796a5.16 5.16 0 0 0 4.362-3.167l8.852-21.37a3.49 3.49 0 0 1 6.448 0l8.852 21.37a5.16 5.16 0 0 0 4.362 3.167l23.073 1.806a3.49 3.49 0 0 1 1.992 6.134L67.906 51.175a5.16 5.16 0 0 0-1.668 5.13l5.41 22.474a3.49 3.49 0 0 1-5.216 3.792L46.693 70.48a5.16 5.16 0 0 0-5.386 0Z" style="mix-blend-mode:soft-light"/><defs><linearGradient id="a" x1="3" x2="84.148" y1="63.5" y2="-1.323" gradientUnits="userSpaceOnUse"><stop stop-color="#6B93FF"/><stop offset=".439" stop-color="#976FFF"/><stop offset="1" stop-color="#E46ACE"/></linearGradient><linearGradient id="b" x1="-7" x2="80.569" y1="102" y2="-4.497" gradientUnits="userSpaceOnUse"><stop stop-color="#FDEB32"/><stop offset=".439" stop-color="#FEBD04"/><stop offset="1" stop-color="#D75902"/></linearGradient><linearGradient id="c" x1="94.417" x2="44.063" y1="26.667" y2="42.313" gradientUnits="userSpaceOnUse"><stop stop-color="#fff" stop-opacity="0"/><stop offset=".396" stop-color="#fff" stop-opacity=".85"/><stop offset=".521" stop-color="#fff"/><stop offset=".646" stop-color="#fff" stop-opacity=".85"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src/assets/stars-bg.png
Normal file
BIN
src/assets/stars-bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@ -25,6 +25,8 @@ export { default as StatusPickerMenu } from '../components/left/main/StatusPicke
|
||||
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 StarsBalanceModal } from '../components/modals/stars/StarsBalanceModal';
|
||||
export { default as StarPaymentModal } from '../components/modals/stars/StarsPaymentModal';
|
||||
|
||||
export { default as AboutAdsModal } from '../components/common/AboutAdsModal';
|
||||
export { default as ReportAdModal } from '../components/modals/reportAd/ReportAdModal';
|
||||
|
||||
@ -12,7 +12,7 @@ import Button from '../ui/Button';
|
||||
import ListItem from '../ui/ListItem';
|
||||
import Modal from '../ui/Modal';
|
||||
import Separator from '../ui/Separator';
|
||||
import Icon from './Icon';
|
||||
import Icon from './icons/Icon';
|
||||
import SafeLink from './SafeLink';
|
||||
|
||||
import styles from './AboutAdsModal.module.scss';
|
||||
|
||||
@ -44,7 +44,7 @@ import Button from '../ui/Button';
|
||||
import Link from '../ui/Link';
|
||||
import ProgressSpinner from '../ui/ProgressSpinner';
|
||||
import AnimatedIcon from './AnimatedIcon';
|
||||
import Icon from './Icon';
|
||||
import Icon from './icons/Icon';
|
||||
|
||||
import './Audio.scss';
|
||||
|
||||
|
||||
@ -36,6 +36,10 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.force-fit &__media {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
@ -5,6 +5,7 @@ import { getActions } from '../../global';
|
||||
|
||||
import type {
|
||||
ApiChat, ApiPeer, ApiPhoto, ApiUser,
|
||||
ApiWebDocument,
|
||||
} from '../../api/types';
|
||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||
import type { CustomPeer, StoryViewerOrigin } from '../../types';
|
||||
@ -16,6 +17,7 @@ import {
|
||||
getChatTitle,
|
||||
getPeerStoryHtmlId,
|
||||
getUserFullName,
|
||||
getWebDocumentHash,
|
||||
isAnonymousForwardsChat,
|
||||
isChatWithRepliesBot,
|
||||
isDeletedUser,
|
||||
@ -34,7 +36,7 @@ import useMediaTransition from '../../hooks/useMediaTransition';
|
||||
|
||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||
import AvatarStoryCircle from './AvatarStoryCircle';
|
||||
import Icon from './Icon';
|
||||
import Icon from './icons/Icon';
|
||||
|
||||
import './Avatar.scss';
|
||||
|
||||
@ -51,6 +53,7 @@ type OwnProps = {
|
||||
size?: AvatarSize;
|
||||
peer?: ApiPeer | CustomPeer;
|
||||
photo?: ApiPhoto;
|
||||
webPhoto?: ApiWebDocument;
|
||||
text?: string;
|
||||
isSavedMessages?: boolean;
|
||||
isSavedDialog?: boolean;
|
||||
@ -74,6 +77,7 @@ const Avatar: FC<OwnProps> = ({
|
||||
size = 'large',
|
||||
peer,
|
||||
photo,
|
||||
webPhoto,
|
||||
text,
|
||||
isSavedMessages,
|
||||
isSavedDialog,
|
||||
@ -118,6 +122,8 @@ const Avatar: FC<OwnProps> = ({
|
||||
if (photo.isVideo && withVideo) {
|
||||
videoHash = `videoAvatar${photo.id}?size=u`;
|
||||
}
|
||||
} else if (webPhoto) {
|
||||
imageHash = getWebDocumentHash(webPhoto);
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,6 +236,7 @@ const Avatar: FC<OwnProps> = ({
|
||||
isReplies && 'replies-bot-account',
|
||||
isPremiumGradient && 'premium-gradient-bg',
|
||||
isRoundedRect && 'forum',
|
||||
(photo || webPhoto) && 'force-fit',
|
||||
((withStory && realPeer?.hasStories) || forPremiumPromo) && 'with-story-circle',
|
||||
withStorySolid && realPeer?.hasStories && 'with-story-solid',
|
||||
withStorySolid && forceFriendStorySolid && 'close-friend',
|
||||
|
||||
31
src/components/common/Blockquote.module.scss
Normal file
31
src/components/common/Blockquote.module.scss
Normal file
@ -0,0 +1,31 @@
|
||||
@use '../../styles/mixins';
|
||||
|
||||
.root {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
@include mixins.gradient-border-bottom(1rem);
|
||||
}
|
||||
|
||||
.gradientContainer {
|
||||
max-height: inherit;
|
||||
}
|
||||
|
||||
.collapseIcon {
|
||||
position: absolute;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 50%;
|
||||
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
}
|
||||
65
src/components/common/Blockquote.tsx
Normal file
65
src/components/common/Blockquote.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import React, {
|
||||
type TeactNode,
|
||||
useRef,
|
||||
} from '../../lib/teact/teact';
|
||||
|
||||
import { ApiMessageEntityTypes } from '../../api/types';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
|
||||
import useCollapsibleLines from '../../hooks/element/useCollapsibleLines';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
|
||||
import Icon from './icons/Icon';
|
||||
|
||||
import styles from './Blockquote.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
canBeCollapsible?: boolean;
|
||||
isToggleDisabled?: boolean;
|
||||
children: TeactNode;
|
||||
};
|
||||
|
||||
const MAX_LINES = 4;
|
||||
|
||||
const Blockquote = ({ canBeCollapsible, isToggleDisabled, children }: OwnProps) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLQuoteElement>(null);
|
||||
const {
|
||||
isCollapsed, isCollapsible, setIsCollapsed,
|
||||
} = useCollapsibleLines(ref, MAX_LINES, undefined, !canBeCollapsible);
|
||||
|
||||
const canExpand = !isToggleDisabled && isCollapsed;
|
||||
|
||||
const handleExpand = useLastCallback(() => {
|
||||
setIsCollapsed(false);
|
||||
});
|
||||
|
||||
const handleToggle = useLastCallback(() => {
|
||||
setIsCollapsed((prev) => !prev);
|
||||
});
|
||||
|
||||
return (
|
||||
<span className={styles.root} onClick={canExpand ? handleExpand : undefined}>
|
||||
<blockquote
|
||||
ref={ref}
|
||||
data-entity-type={ApiMessageEntityTypes.Blockquote}
|
||||
>
|
||||
<div className={buildClassName(styles.gradientContainer, isCollapsed && styles.collapsed)}>
|
||||
{children}
|
||||
</div>
|
||||
{isCollapsible && (
|
||||
<div
|
||||
className={buildClassName(styles.collapseIcon, !isToggleDisabled && styles.clickable)}
|
||||
onClick={!isToggleDisabled ? handleToggle : undefined}
|
||||
aria-hidden
|
||||
>
|
||||
<Icon name={isCollapsed ? 'down' : 'up'} />
|
||||
</div>
|
||||
)}
|
||||
</blockquote>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default Blockquote;
|
||||
@ -158,7 +158,7 @@ import ResponsiveHoverButton from '../ui/ResponsiveHoverButton';
|
||||
import Spinner from '../ui/Spinner';
|
||||
import Avatar from './Avatar';
|
||||
import DeleteMessageModal from './DeleteMessageModal.async';
|
||||
import Icon from './Icon';
|
||||
import Icon from './icons/Icon';
|
||||
import ReactionAnimatedEmoji from './reactions/ReactionAnimatedEmoji';
|
||||
|
||||
import './Composer.scss';
|
||||
|
||||
@ -14,7 +14,7 @@ import usePrevious from '../../hooks/usePrevious';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
import Modal from '../ui/Modal';
|
||||
import Icon from './Icon';
|
||||
import Icon from './icons/Icon';
|
||||
import Picker from './Picker';
|
||||
|
||||
import styles from './CountryPickerModal.module.scss';
|
||||
|
||||
@ -43,7 +43,7 @@ import { useStickerPickerObservers } from './hooks/useStickerPickerObservers';
|
||||
import StickerSetCover from '../middle/composer/StickerSetCover';
|
||||
import Button from '../ui/Button';
|
||||
import Loading from '../ui/Loading';
|
||||
import Icon from './Icon';
|
||||
import Icon from './icons/Icon';
|
||||
import StickerButton from './StickerButton';
|
||||
import StickerSet from './StickerSet';
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ import useLastCallback from '../../hooks/useLastCallback';
|
||||
|
||||
import CustomEmoji from './CustomEmoji';
|
||||
import FakeIcon from './FakeIcon';
|
||||
import PremiumIcon from './PremiumIcon';
|
||||
import StarIcon from './icons/StarIcon';
|
||||
import VerifiedIcon from './VerifiedIcon';
|
||||
|
||||
import styles from './FullNameTitle.module.scss';
|
||||
@ -127,7 +127,7 @@ const FullNameTitle: FC<OwnProps> = ({
|
||||
onClick={onEmojiStatusClick}
|
||||
/>
|
||||
)}
|
||||
{withEmojiStatus && !realPeer?.emojiStatus && isPremium && <PremiumIcon />}
|
||||
{withEmojiStatus && !realPeer?.emojiStatus && isPremium && <StarIcon />}
|
||||
</>
|
||||
)}
|
||||
{iconElement}
|
||||
|
||||
@ -33,7 +33,7 @@ import Transition from '../ui/Transition';
|
||||
import Avatar from './Avatar';
|
||||
import DotAnimation from './DotAnimation';
|
||||
import FullNameTitle from './FullNameTitle';
|
||||
import Icon from './Icon';
|
||||
import Icon from './icons/Icon';
|
||||
import TopicIcon from './TopicIcon';
|
||||
import TypingStatus from './TypingStatus';
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ import useLastCallback from '../../hooks/useLastCallback';
|
||||
import Button from '../ui/Button';
|
||||
import DropdownMenu from '../ui/DropdownMenu';
|
||||
import MenuItem from '../ui/MenuItem';
|
||||
import Icon from './Icon';
|
||||
import Icon from './icons/Icon';
|
||||
|
||||
import styles from './LinkField.module.scss';
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import React, { memo } from '../../lib/teact/teact';
|
||||
import type { ApiMessageOutgoingStatus } from '../../api/types';
|
||||
|
||||
import Transition from '../ui/Transition';
|
||||
import Icon from './Icon';
|
||||
import Icon from './icons/Icon';
|
||||
|
||||
import './MessageOutgoingStatus.scss';
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ interface OwnProps {
|
||||
inChatList?: boolean;
|
||||
forcePlayback?: boolean;
|
||||
focusedQuote?: string;
|
||||
isInSelectMode?: boolean;
|
||||
}
|
||||
|
||||
const MIN_CUSTOM_EMOJIS_FOR_SHARED_CANVAS = 3;
|
||||
@ -49,6 +50,7 @@ function MessageText({
|
||||
inChatList,
|
||||
forcePlayback,
|
||||
focusedQuote,
|
||||
isInSelectMode,
|
||||
}: OwnProps) {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const sharedCanvasRef = useRef<HTMLCanvasElement>(null);
|
||||
@ -104,6 +106,7 @@ function MessageText({
|
||||
cacheBuster: textCacheBusterRef.current.toString(),
|
||||
forcePlayback,
|
||||
focusedQuote,
|
||||
isInSelectMode,
|
||||
}),
|
||||
].flat().filter(Boolean)}
|
||||
</>
|
||||
|
||||
@ -1,3 +1,22 @@
|
||||
.root {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
padding-inline-start: 0.5rem;
|
||||
|
||||
border-radius: 0.25rem;
|
||||
|
||||
background-color: var(--accent-background-color);
|
||||
color: var(--accent-color);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
inset-inline-start: 0;
|
||||
width: 0.1875rem;
|
||||
background: var(--bar-gradient, var(--accent-color));
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import React, {
|
||||
} from '../../lib/teact/teact';
|
||||
|
||||
import type { ApiCountry } from '../../api/types';
|
||||
import type { CustomPeer, CustomPeerType } from '../../types';
|
||||
import type { CustomPeer, CustomPeerType, UniqueCustomPeer } from '../../types';
|
||||
|
||||
import { requestMeasure } from '../../lib/fasterdom/fasterdom';
|
||||
import { isUserId } from '../../global/helpers';
|
||||
@ -28,7 +28,7 @@ import './Picker.scss';
|
||||
|
||||
type OwnProps = {
|
||||
className?: string;
|
||||
categories?: CustomPeer[];
|
||||
categories?: UniqueCustomPeer[];
|
||||
itemIds: string[];
|
||||
selectedCategories?: CustomPeerType[];
|
||||
selectedIds: string[];
|
||||
|
||||
@ -15,7 +15,7 @@ import renderText from './helpers/renderText';
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import Avatar from './Avatar';
|
||||
import Icon from './Icon';
|
||||
import Icon from './icons/Icon';
|
||||
|
||||
import './PickerSelectedItem.scss';
|
||||
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
.PremiumIcon {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
&.big {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
--color-fill: var(--color-primary);
|
||||
|
||||
& > svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.clickable {
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
|
||||
import useUniqueId from '../../hooks/useUniqueId';
|
||||
|
||||
import './PremiumIcon.scss';
|
||||
|
||||
type OwnProps = {
|
||||
withGradient?: boolean;
|
||||
big?: boolean;
|
||||
className?: string;
|
||||
onClick?: VoidFunction;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
const STAR_PATH = 'M6.63869 12.1902L3.50621 14.1092C3.18049 14.3087 2.75468 14.2064 2.55515 13.8807C2.45769 13.7216 2.42864 13.5299 2.47457 13.3491L2.95948 11.4405C3.13452 10.7515 3.60599 10.1756 4.24682 9.86791L7.6642 8.22716C7.82352 8.15067 7.89067 7.95951 7.81418 7.80019C7.75223 7.67116 7.61214 7.59896 7.47111 7.62338L3.66713 8.28194C2.89387 8.41581 2.1009 8.20228 1.49941 7.69823L0.297703 6.69116C0.00493565 6.44581 -0.0335059 6.00958 0.211842 5.71682C0.33117 5.57442 0.502766 5.48602 0.687982 5.47153L4.35956 5.18419C4.61895 5.16389 4.845 4.99974 4.94458 4.75937L6.36101 1.3402C6.5072 0.987302 6.91179 0.819734 7.26469 0.965925C7.43413 1.03612 7.56876 1.17075 7.63896 1.3402L9.05539 4.75937C9.15496 4.99974 9.38101 5.16389 9.6404 5.18419L13.3322 5.47311C13.713 5.50291 13.9975 5.83578 13.9677 6.2166C13.9534 6.39979 13.8667 6.56975 13.7269 6.68896L10.9114 9.08928C10.7131 9.25826 10.6267 9.52425 10.6876 9.77748L11.5532 13.3733C11.6426 13.7447 11.414 14.1182 11.0427 14.2076C10.8642 14.2506 10.676 14.2208 10.5195 14.1249L7.36128 12.1902C7.13956 12.0544 6.8604 12.0544 6.63869 12.1902Z';
|
||||
|
||||
const PremiumIcon: FC<OwnProps> = ({
|
||||
withGradient,
|
||||
big,
|
||||
className,
|
||||
onClick,
|
||||
}) => {
|
||||
const randomId = useUniqueId();
|
||||
|
||||
return (
|
||||
<i
|
||||
onClick={onClick}
|
||||
className={buildClassName(
|
||||
'PremiumIcon', className, withGradient && 'gradient', onClick && 'clickable', big && 'big',
|
||||
)}
|
||||
title="Premium"
|
||||
>
|
||||
{withGradient ? (
|
||||
<svg width="14" height="15" viewBox="0 0 14 15" fill="none">
|
||||
<defs>
|
||||
<linearGradient id={randomId} x1="3" y1="63.5001" x2="84.1475" y2="-1.32262" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6B93FF" />
|
||||
<stop offset="0.439058" stop-color="#976FFF" />
|
||||
<stop offset="1" stop-color="#E46ACE" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d={STAR_PATH} fill={`url(#${randomId})`} />
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="14" height="15" viewBox="0 0 14 15" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d={STAR_PATH} fill="var(--color-fill)" />
|
||||
</svg>
|
||||
)}
|
||||
</i>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(PremiumIcon);
|
||||
@ -11,7 +11,7 @@ import buildStyle from '../../util/buildStyle';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useResizeObserver from '../../hooks/useResizeObserver';
|
||||
|
||||
import Icon from './Icon';
|
||||
import Icon from './icons/Icon';
|
||||
|
||||
import styles from './PremiumProgress.module.scss';
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ import Button from '../ui/Button';
|
||||
import Modal, { ANIMATION_DURATION } from '../ui/Modal';
|
||||
import Separator from '../ui/Separator';
|
||||
import AnimatedIconWithPreview from './AnimatedIconWithPreview';
|
||||
import Icon from './Icon';
|
||||
import Icon from './icons/Icon';
|
||||
|
||||
import styles from './PrivacySettingsNoticeModal.module.scss';
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ import RippleEffect from '../ui/RippleEffect';
|
||||
import Avatar from './Avatar';
|
||||
import DotAnimation from './DotAnimation';
|
||||
import FullNameTitle from './FullNameTitle';
|
||||
import Icon from './Icon';
|
||||
import Icon from './icons/Icon';
|
||||
import TypingStatus from './TypingStatus';
|
||||
|
||||
type OwnProps = {
|
||||
@ -227,7 +227,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
)}
|
||||
<Avatar
|
||||
key={customPeer?.type || user?.id}
|
||||
key={user?.id}
|
||||
size={avatarSize}
|
||||
peer={customPeer || user}
|
||||
className={buildClassName(isSavedDialog && 'overlay-avatar')}
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
}
|
||||
|
||||
.VerifiedIcon,
|
||||
.PremiumIcon {
|
||||
.StarIcon {
|
||||
--color-fill: var(--color-white);
|
||||
--color-checkmark: var(--color-primary);
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ import useMediaTransition from '../../hooks/useMediaTransition';
|
||||
|
||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||
import Spinner from '../ui/Spinner';
|
||||
import Icon from './Icon';
|
||||
import Icon from './icons/Icon';
|
||||
|
||||
import './ProfilePhoto.scss';
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import type { TeactNode } from '../../lib/teact/teact';
|
||||
import React from '../../lib/teact/teact';
|
||||
import { getActions } from '../../global';
|
||||
|
||||
import { ApiMessageEntityTypes } from '../../api/types';
|
||||
@ -17,17 +17,17 @@ type OwnProps = {
|
||||
url?: string;
|
||||
text: string;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
children?: TeactNode;
|
||||
isRtl?: boolean;
|
||||
};
|
||||
|
||||
const SafeLink: FC<OwnProps> = ({
|
||||
const SafeLink = ({
|
||||
url,
|
||||
text,
|
||||
className,
|
||||
children,
|
||||
isRtl,
|
||||
}) => {
|
||||
}: OwnProps) => {
|
||||
const { openUrl } = getActions();
|
||||
|
||||
const content = children || text;
|
||||
@ -96,4 +96,4 @@ function getUnicodeUrl(url?: string) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export default memo(SafeLink);
|
||||
export default SafeLink;
|
||||
|
||||
@ -37,7 +37,7 @@ import useThumbnail from '../../../hooks/useThumbnail';
|
||||
import useMessageTranslation from '../../middle/message/hooks/useMessageTranslation';
|
||||
|
||||
import ActionMessage from '../../middle/ActionMessage';
|
||||
import Icon from '../Icon';
|
||||
import Icon from '../icons/Icon';
|
||||
import MediaSpoiler from '../MediaSpoiler';
|
||||
import MessageSummary from '../MessageSummary';
|
||||
import EmojiIconBackground from './EmojiIconBackground';
|
||||
|
||||
@ -20,7 +20,7 @@ import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useMedia from '../../../hooks/useMedia';
|
||||
|
||||
import Icon from '../Icon';
|
||||
import Icon from '../icons/Icon';
|
||||
|
||||
import './EmbeddedMessage.scss';
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ import { useFastClick } from '../../../hooks/useFastClick';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Icon from '../Icon';
|
||||
import Icon from '../icons/Icon';
|
||||
import EmojiIconBackground from './EmojiIconBackground';
|
||||
|
||||
import './EmbeddedMessage.scss';
|
||||
|
||||
@ -14,6 +14,7 @@ import { buildCustomEmojiHtmlFromEntity } from '../../middle/composer/helpers/cu
|
||||
import renderText from './renderText';
|
||||
|
||||
import MentionLink from '../../middle/message/MentionLink';
|
||||
import Blockquote from '../Blockquote';
|
||||
import CodeBlock from '../code/CodeBlock';
|
||||
import CustomEmoji from '../CustomEmoji';
|
||||
import SafeLink from '../SafeLink';
|
||||
@ -46,6 +47,7 @@ export function renderTextWithEntities({
|
||||
cacheBuster,
|
||||
forcePlayback,
|
||||
focusedQuote,
|
||||
isInSelectMode,
|
||||
}: {
|
||||
text: string;
|
||||
entities?: ApiMessageEntity[];
|
||||
@ -64,6 +66,7 @@ export function renderTextWithEntities({
|
||||
cacheBuster?: string;
|
||||
forcePlayback?: boolean;
|
||||
focusedQuote?: string;
|
||||
isInSelectMode?: boolean;
|
||||
}) {
|
||||
if (!entities?.length) {
|
||||
return renderMessagePart({
|
||||
@ -170,6 +173,7 @@ export function renderTextWithEntities({
|
||||
sharedCanvasHqRef,
|
||||
cacheBuster,
|
||||
forcePlayback,
|
||||
isInSelectMode,
|
||||
});
|
||||
|
||||
if (Array.isArray(newEntity)) {
|
||||
@ -384,6 +388,7 @@ function processEntity({
|
||||
sharedCanvasHqRef,
|
||||
cacheBuster,
|
||||
forcePlayback,
|
||||
isInSelectMode,
|
||||
} : {
|
||||
entity: ApiMessageEntity;
|
||||
entityContent: TextPart;
|
||||
@ -402,6 +407,7 @@ function processEntity({
|
||||
sharedCanvasHqRef?: React.RefObject<HTMLCanvasElement>;
|
||||
cacheBuster?: string;
|
||||
forcePlayback?: boolean;
|
||||
isInSelectMode?: boolean;
|
||||
}) {
|
||||
const entityText = typeof entityContent === 'string' && entityContent;
|
||||
const renderedContent = nestedEntityContent.length ? nestedEntityContent : entityContent;
|
||||
@ -451,11 +457,9 @@ function processEntity({
|
||||
return <strong data-entity-type={entity.type}>{renderNestedMessagePart()}</strong>;
|
||||
case ApiMessageEntityTypes.Blockquote:
|
||||
return (
|
||||
<span className="text-entity-blockquote-wrapper">
|
||||
<blockquote data-entity-type={entity.type}>
|
||||
{renderNestedMessagePart()}
|
||||
</blockquote>
|
||||
</span>
|
||||
<Blockquote canBeCollapsible={entity.canCollapse} isToggleDisabled={isInSelectMode}>
|
||||
{renderNestedMessagePart()}
|
||||
</Blockquote>
|
||||
);
|
||||
case ApiMessageEntityTypes.BotCommand:
|
||||
return (
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { AriaRole } from 'react';
|
||||
import React from '../../lib/teact/teact';
|
||||
import React from '../../../lib/teact/teact';
|
||||
|
||||
import type { IconName } from '../../types/icons';
|
||||
import type { IconName } from '../../../types/icons';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
type OwnProps = {
|
||||
name: IconName;
|
||||
28
src/components/common/icons/StarIcon.module.scss
Normal file
28
src/components/common/icons/StarIcon.module.scss
Normal file
@ -0,0 +1,28 @@
|
||||
.root {
|
||||
--color-fill: var(--color-primary);
|
||||
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.middle {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.big {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
pointer-events: auto;
|
||||
}
|
||||
139
src/components/common/icons/StarIcon.tsx
Normal file
139
src/components/common/icons/StarIcon.tsx
Normal file
@ -0,0 +1,139 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo } from '../../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import useUniqueId from '../../../hooks/useUniqueId';
|
||||
|
||||
import styles from './StarIcon.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
type?: 'gold' | 'premium' | 'regular';
|
||||
size?: 'small' | 'middle' | 'big';
|
||||
className?: string;
|
||||
onClick?: VoidFunction;
|
||||
};
|
||||
|
||||
/* eslint-disable max-len */
|
||||
const STAR_PATH = 'M6.63869 12.1902L3.50621 14.1092C3.18049 14.3087 2.75468 14.2064 2.55515 13.8807C2.45769 13.7216 2.42864 13.5299 2.47457 13.3491L2.95948 11.4405C3.13452 10.7515 3.60599 10.1756 4.24682 9.86791L7.6642 8.22716C7.82352 8.15067 7.89067 7.95951 7.81418 7.80019C7.75223 7.67116 7.61214 7.59896 7.47111 7.62338L3.66713 8.28194C2.89387 8.41581 2.1009 8.20228 1.49941 7.69823L0.297703 6.69116C0.00493565 6.44581 -0.0335059 6.00958 0.211842 5.71682C0.33117 5.57442 0.502766 5.48602 0.687982 5.47153L4.35956 5.18419C4.61895 5.16389 4.845 4.99974 4.94458 4.75937L6.36101 1.3402C6.5072 0.987302 6.91179 0.819734 7.26469 0.965925C7.43413 1.03612 7.56876 1.17075 7.63896 1.3402L9.05539 4.75937C9.15496 4.99974 9.38101 5.16389 9.6404 5.18419L13.3322 5.47311C13.713 5.50291 13.9975 5.83578 13.9677 6.2166C13.9534 6.39979 13.8667 6.56975 13.7269 6.68896L10.9114 9.08928C10.7131 9.25826 10.6267 9.52425 10.6876 9.77748L11.5532 13.3733C11.6426 13.7447 11.414 14.1182 11.0427 14.2076C10.8642 14.2506 10.676 14.2208 10.5195 14.1249L7.36128 12.1902C7.13956 12.0544 6.8604 12.0544 6.63869 12.1902Z';
|
||||
const GOLD_STAR_PATH = 'M10.5197 16.2049L6.46899 18.6864C6.04779 18.9444 5.49716 18.8121 5.23913 18.3909C5.11311 18.1852 5.07554 17.9373 5.13494 17.7035L5.762 15.2354C5.98835 14.3444 6.59803 13.5997 7.42671 13.2018L11.8459 11.0801C12.0519 10.9812 12.1387 10.734 12.0398 10.528C11.9597 10.3611 11.7786 10.2677 11.5962 10.2993L6.67709 11.1509C5.67715 11.324 4.65172 11.0479 3.87392 10.3961L2.31994 9.09382C1.94135 8.77655 1.89164 8.21245 2.20891 7.83386C2.36321 7.64972 2.58511 7.53541 2.82462 7.51667L7.5725 7.1451C7.90793 7.11885 8.20025 6.90658 8.32901 6.59574L10.1607 2.17427C10.3497 1.71792 10.8729 1.50123 11.3292 1.69028C11.5484 1.78105 11.7225 1.95514 11.8132 2.17427L13.6449 6.59574C13.7736 6.90658 14.066 7.11885 14.4014 7.1451L19.1754 7.51871C19.6678 7.55725 20.0358 7.9877 19.9972 8.48015C19.9787 8.71704 19.8666 8.93682 19.6858 9.09098L16.0449 12.1949C15.7886 12.4134 15.6768 12.7574 15.7556 13.0849L16.8749 17.7348C16.9905 18.215 16.6949 18.698 16.2147 18.8137C15.9839 18.8692 15.7406 18.8307 15.5382 18.7068L11.4541 16.2049C11.1674 16.0292 10.8064 16.0292 10.5197 16.2049Z';
|
||||
/* eslint-enable max-len */
|
||||
|
||||
const StarIcon: FC<OwnProps> = ({
|
||||
type = 'regular',
|
||||
size = 'small',
|
||||
className,
|
||||
onClick,
|
||||
}) => {
|
||||
const randomId = useUniqueId();
|
||||
const validSvgRandomId = `svg-${randomId}`; // ID must start with a letter
|
||||
|
||||
return (
|
||||
<i
|
||||
onClick={onClick}
|
||||
className={buildClassName(
|
||||
'StarIcon',
|
||||
styles.root,
|
||||
className,
|
||||
onClick && styles.clickable,
|
||||
styles[size],
|
||||
)}
|
||||
>
|
||||
{type === 'gold'
|
||||
? <GoldStarIcon randomId={validSvgRandomId} />
|
||||
: type === 'premium'
|
||||
? <PremiumStarIcon randomId={validSvgRandomId} />
|
||||
: <RegularStarIcon />}
|
||||
</i>
|
||||
);
|
||||
};
|
||||
|
||||
function GoldStarIcon({ randomId }: { randomId: string }) {
|
||||
const fillId = `${randomId}-fill`;
|
||||
const stroke1Id = `${randomId}-stroke1`;
|
||||
const stroke2Id = `${randomId}-stroke2`;
|
||||
|
||||
return (
|
||||
<svg className={styles.svg} width="21" height="20" viewBox="0 0 21 20" fill="none">
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={fillId}
|
||||
x1="0.434893"
|
||||
y1="22.5796"
|
||||
x2="34.2364"
|
||||
y2="-15.5089"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#FDEB32" />
|
||||
<stop offset="0.439058" stop-color="#FEBD04" />
|
||||
<stop offset="1" stop-color="#D75902" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id={stroke1Id}
|
||||
x1="22.5"
|
||||
y1="2.5"
|
||||
x2="8"
|
||||
y2="12.5"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#DB5A00" />
|
||||
<stop offset="1" stop-color="#FF9145" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id={stroke2Id}
|
||||
x1="24.5"
|
||||
y1="2"
|
||||
x2="11"
|
||||
y2="10.2302"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" stop-opacity="0" />
|
||||
<stop offset="0.395833" stop-color="white" stop-opacity="0.85" />
|
||||
<stop offset="0.520833" stop-color="white" />
|
||||
<stop offset="0.645833" stop-color="white" stop-opacity="0.85" />
|
||||
<stop offset="1" stop-color="white" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d={GOLD_STAR_PATH}
|
||||
fill={`url(#${fillId})`}
|
||||
stroke={`url(#${stroke1Id})`}
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d={GOLD_STAR_PATH}
|
||||
stroke={`url(#${stroke2Id})`}
|
||||
stroke-width="2"
|
||||
style="mix-blend-mode:soft-light"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function PremiumStarIcon({ randomId }: { randomId: string }) {
|
||||
return (
|
||||
<svg className={styles.svg} width="14" height="15" viewBox="0 0 14 15" fill="none">
|
||||
<defs>
|
||||
<linearGradient id={randomId} x1="3" y1="63.5001" x2="84.1475" y2="-1.32262" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6B93FF" />
|
||||
<stop offset="0.439058" stop-color="#976FFF" />
|
||||
<stop offset="1" stop-color="#E46ACE" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d={STAR_PATH} fill={`url(#${randomId})`} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function RegularStarIcon() {
|
||||
return (
|
||||
<svg className={styles.svg} width="14" height="15" viewBox="0 0 14 15" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d={STAR_PATH} fill="var(--color-fill)" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(StarIcon);
|
||||
@ -22,7 +22,7 @@ import useSelectorSignal from '../../../hooks/useSelectorSignal';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Transition, { ACTIVE_SLIDE_CLASS_NAME, TO_SLIDE_CLASS_NAME } from '../../ui/Transition';
|
||||
import Icon from '../Icon';
|
||||
import Icon from '../icons/Icon';
|
||||
|
||||
import styles from './BusinessHours.module.scss';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import type { TeactNode } from '../../../lib/teact/teact';
|
||||
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
|
||||
|
||||
import { ApiMessageEntityTypes } from '../../../api/types';
|
||||
@ -11,7 +11,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import './Spoiler.scss';
|
||||
|
||||
type OwnProps = {
|
||||
children?: React.ReactNode;
|
||||
children?: TeactNode;
|
||||
containerId?: string;
|
||||
};
|
||||
|
||||
@ -19,10 +19,10 @@ const revealByContainerId: Map<string, VoidFunction[]> = new Map();
|
||||
|
||||
const buildClassName = createClassNameBuilder('Spoiler');
|
||||
|
||||
const Spoiler: FC<OwnProps> = ({
|
||||
const Spoiler = ({
|
||||
children,
|
||||
containerId,
|
||||
}) => {
|
||||
}: OwnProps) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
||||
@ -89,7 +89,7 @@
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.VerifiedIcon, .PremiumIcon {
|
||||
.VerifiedIcon, .StarIcon {
|
||||
--color-fill: #fff;
|
||||
--color-checkmark: var(--color-primary);
|
||||
}
|
||||
|
||||
@ -121,7 +121,7 @@
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.PremiumIcon {
|
||||
.StarIcon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
|
||||
import CustomEmoji from '../../common/CustomEmoji';
|
||||
import PremiumIcon from '../../common/PremiumIcon';
|
||||
import StarIcon from '../../common/icons/StarIcon';
|
||||
import CustomEmojiEffect from '../../common/reactions/CustomEmojiEffect';
|
||||
import Button from '../../ui/Button';
|
||||
import StatusPickerMenu from './StatusPickerMenu.async';
|
||||
@ -82,7 +82,7 @@ const StatusButton: FC<StateProps> = ({ emojiStatus }) => {
|
||||
size={EMOJI_STATUS_SIZE}
|
||||
loopLimit={EMOJI_STATUS_LOOP_LIMIT}
|
||||
/>
|
||||
) : <PremiumIcon />}
|
||||
) : <StarIcon />}
|
||||
</Button>
|
||||
<StatusPickerMenu
|
||||
statusButtonRef={buttonRef}
|
||||
|
||||
@ -6,7 +6,7 @@ import type { ApiPremiumSection } from '../../../global/types';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import PremiumIcon from '../../common/PremiumIcon';
|
||||
import StarIcon from '../../common/icons/StarIcon';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
|
||||
type OwnProps = {
|
||||
@ -21,7 +21,7 @@ function PremiumStatusItem({ premiumSection }: OwnProps) {
|
||||
return (
|
||||
<div className="settings-item">
|
||||
<ListItem
|
||||
leftElement={<PremiumIcon className="icon" withGradient big />}
|
||||
leftElement={<StarIcon className="icon" type="premium" size="big" />}
|
||||
onClick={handleOpenPremiumModal}
|
||||
>
|
||||
{lang('PrivacyLastSeenPremium')}
|
||||
|
||||
@ -3,7 +3,7 @@ import { getActions } from '../../../global';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import Icon from '../../common/Icon';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
|
||||
import styles from './PrivacyLockedOption.module.scss';
|
||||
|
||||
|
||||
@ -103,7 +103,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.settings-main-menu-premium .PremiumIcon {
|
||||
.settings-main-menu-star .StarIcon {
|
||||
margin-right: 1.25rem;
|
||||
}
|
||||
|
||||
|
||||
@ -78,7 +78,7 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
|
||||
<div className="settings-item">
|
||||
<ListItem
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => requestConfetti({})}
|
||||
onClick={() => requestConfetti({ withStars: true })}
|
||||
icon="animations"
|
||||
>
|
||||
<div className="title">Launch some confetti!</div>
|
||||
|
||||
@ -9,13 +9,14 @@ import {
|
||||
selectIsGiveawayGiftsPurchaseAvailable,
|
||||
selectIsPremiumPurchaseBlocked,
|
||||
} from '../../../global/selectors';
|
||||
import { formatInteger } from '../../../util/textFormat';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import PremiumIcon from '../../common/PremiumIcon';
|
||||
import StarIcon from '../../common/icons/StarIcon';
|
||||
import ChatExtra from '../../common/profile/ChatExtra';
|
||||
import ProfileInfo from '../../common/ProfileInfo';
|
||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||
@ -32,16 +33,20 @@ type StateProps = {
|
||||
currentUserId?: string;
|
||||
canBuyPremium?: boolean;
|
||||
isGiveawayAvailable?: boolean;
|
||||
starsBalance?: number;
|
||||
shouldDisplayStars?: boolean;
|
||||
};
|
||||
|
||||
const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
currentUserId,
|
||||
sessionCount,
|
||||
canBuyPremium,
|
||||
isGiveawayAvailable,
|
||||
starsBalance,
|
||||
shouldDisplayStars,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
}) => {
|
||||
const {
|
||||
loadProfilePhotos,
|
||||
@ -49,6 +54,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
openSupportChat,
|
||||
openUrl,
|
||||
openPremiumGiftingModal,
|
||||
openStarsBalanceModal,
|
||||
} = getActions();
|
||||
|
||||
const [isSupportDialogOpen, openSupportDialog, closeSupportDialog] = useFlag(false);
|
||||
@ -156,18 +162,31 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
<div className="settings-main-menu">
|
||||
{canBuyPremium && (
|
||||
<ListItem
|
||||
leftElement={<PremiumIcon className="icon" withGradient big />}
|
||||
className="settings-main-menu-premium"
|
||||
leftElement={<StarIcon className="icon" type="premium" size="big" />}
|
||||
className="settings-main-menu-star"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => openPremiumModal()}
|
||||
>
|
||||
{lang('TelegramPremium')}
|
||||
</ListItem>
|
||||
)}
|
||||
{shouldDisplayStars && (
|
||||
<ListItem
|
||||
leftElement={<StarIcon className="icon" type="gold" size="big" />}
|
||||
className="settings-main-menu-star"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => openStarsBalanceModal({})}
|
||||
>
|
||||
{lang('MenuTelegramStars')}
|
||||
{Boolean(starsBalance) && (
|
||||
<span className="settings-item__current-value">{formatInteger(starsBalance)}</span>
|
||||
)}
|
||||
</ListItem>
|
||||
)}
|
||||
{isGiveawayAvailable && (
|
||||
<ListItem
|
||||
icon="gift"
|
||||
className="settings-main-menu-premium"
|
||||
className="settings-main-menu-star"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => openPremiumGiftingModal()}
|
||||
>
|
||||
@ -213,12 +232,16 @@ export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const { currentUserId } = global;
|
||||
const isGiveawayAvailable = selectIsGiveawayGiftsPurchaseAvailable(global);
|
||||
const starsBalance = global.stars?.balance;
|
||||
const shouldDisplayStars = Boolean(global.stars?.history?.all?.transactions.length);
|
||||
|
||||
return {
|
||||
sessionCount: global.activeSessions.orderedHashes.length,
|
||||
currentUserId,
|
||||
canBuyPremium: !selectIsPremiumPurchaseBlocked(global),
|
||||
isGiveawayAvailable,
|
||||
starsBalance,
|
||||
shouldDisplayStars,
|
||||
};
|
||||
},
|
||||
)(SettingsMain));
|
||||
|
||||
@ -11,7 +11,7 @@ import { selectCanSetPasscode, selectIsCurrentUserPremium } from '../../../globa
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import PremiumIcon from '../../common/PremiumIcon';
|
||||
import StarIcon from '../../common/icons/StarIcon';
|
||||
import Checkbox from '../../ui/Checkbox';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
|
||||
@ -282,7 +282,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
||||
<ListItem
|
||||
narrow
|
||||
allowDisabledClick
|
||||
rightElement={isCurrentUserPremium && <PremiumIcon big withGradient />}
|
||||
rightElement={isCurrentUserPremium && <StarIcon size="big" type="premium" />}
|
||||
className="no-icon"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => onScreenSelect(SettingsScreens.PrivacyVoiceMessages)}
|
||||
@ -296,7 +296,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
||||
</ListItem>
|
||||
<ListItem
|
||||
narrow
|
||||
rightElement={isCurrentUserPremium && <PremiumIcon big withGradient />}
|
||||
rightElement={isCurrentUserPremium && <StarIcon size="big" type="premium" />}
|
||||
className="no-icon"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => onScreenSelect(SettingsScreens.PrivacyMessages)}
|
||||
|
||||
@ -9,7 +9,7 @@ import renderText from '../../common/helpers/renderText';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import PremiumIcon from '../../common/PremiumIcon';
|
||||
import StarIcon from '../../common/icons/StarIcon';
|
||||
import Checkbox from '../../ui/Checkbox';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
|
||||
@ -55,7 +55,7 @@ const SettingsPrivacyLastSeen = ({
|
||||
)}
|
||||
<div className="settings-item">
|
||||
<ListItem
|
||||
leftElement={<PremiumIcon className="icon" withGradient big />}
|
||||
leftElement={<StarIcon className="icon" type="premium" size="big" />}
|
||||
onClick={handleOpenPremiumModal}
|
||||
>
|
||||
{isCurrentUserPremium ? lang('PrivacyLastSeenPremiumForPremium') : lang('PrivacyLastSeenPremium')}
|
||||
|
||||
@ -21,7 +21,7 @@ import useFlag from '../../hooks/useFlag';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
|
||||
import Icon from '../common/Icon';
|
||||
import Icon from '../common/icons/Icon';
|
||||
import Picker from '../common/Picker';
|
||||
import Button from '../ui/Button';
|
||||
import ConfirmDialog from '../ui/ConfirmDialog';
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo, useRef } from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../global';
|
||||
|
||||
@ -32,6 +31,7 @@ interface Confetti {
|
||||
};
|
||||
size: number;
|
||||
color: string;
|
||||
isStar?: boolean;
|
||||
flicker: number;
|
||||
flickerFrequency: number;
|
||||
rotation: number;
|
||||
@ -42,8 +42,11 @@ interface Confetti {
|
||||
const CONFETTI_FADEOUT_TIMEOUT = 10000;
|
||||
const DEFAULT_CONFETTI_SIZE = 10;
|
||||
const CONFETTI_COLORS = ['#E8BC2C', '#D0049E', '#02CBFE', '#5723FD', '#FE8C27', '#6CB859'];
|
||||
// eslint-disable-next-line max-len
|
||||
const STAR_PATH = new Path2D('M6.63869 12.1902L3.50621 14.1092C3.18049 14.3087 2.75468 14.2064 2.55515 13.8807C2.45769 13.7216 2.42864 13.5299 2.47457 13.3491L2.95948 11.4405C3.13452 10.7515 3.60599 10.1756 4.24682 9.86791L7.6642 8.22716C7.82352 8.15067 7.89067 7.95951 7.81418 7.80019C7.75223 7.67116 7.61214 7.59896 7.47111 7.62338L3.66713 8.28194C2.89387 8.41581 2.1009 8.20228 1.49941 7.69823L0.297703 6.69116C0.00493565 6.44581 -0.0335059 6.00958 0.211842 5.71682C0.33117 5.57442 0.502766 5.48602 0.687982 5.47153L4.35956 5.18419C4.61895 5.16389 4.845 4.99974 4.94458 4.75937L6.36101 1.3402C6.5072 0.987302 6.91179 0.819734 7.26469 0.965925C7.43413 1.03612 7.56876 1.17075 7.63896 1.3402L9.05539 4.75937C9.15496 4.99974 9.38101 5.16389 9.6404 5.18419L13.3322 5.47311C13.713 5.50291 13.9975 5.83578 13.9677 6.2166C13.9534 6.39979 13.8667 6.56975 13.7269 6.68896L10.9114 9.08928C10.7131 9.25826 10.6267 9.52425 10.6876 9.77748L11.5532 13.3733C11.6426 13.7447 11.414 14.1182 11.0427 14.2076C10.8642 14.2506 10.676 14.2208 10.5195 14.1249L7.36128 12.1902C7.13956 12.0544 6.8604 12.0544 6.63869 12.1902Z');
|
||||
const STAR_SIZE_MULTIPLIER = 1.5;
|
||||
|
||||
const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
|
||||
const ConfettiContainer = ({ confetti }: StateProps) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const confettiRef = useRef<Confetti[]>([]);
|
||||
@ -76,6 +79,7 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
|
||||
rotation: 0,
|
||||
lastDrawnAt: Date.now(),
|
||||
frameCount: 0,
|
||||
isStar: confetti?.withStars && Math.random() > 0.8,
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -143,17 +147,29 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
|
||||
|
||||
confettiRef.current[i] = newConfetti;
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(
|
||||
pos.x,
|
||||
pos.y,
|
||||
size,
|
||||
flicker,
|
||||
rotation,
|
||||
0,
|
||||
2 * Math.PI,
|
||||
);
|
||||
ctx.fill();
|
||||
if (c.isStar) {
|
||||
ctx.save();
|
||||
ctx.translate(pos.x, pos.y);
|
||||
ctx.scale(
|
||||
(size / DEFAULT_CONFETTI_SIZE) * STAR_SIZE_MULTIPLIER,
|
||||
(size / DEFAULT_CONFETTI_SIZE) * STAR_SIZE_MULTIPLIER,
|
||||
);
|
||||
ctx.rotate(rotation);
|
||||
ctx.fill(STAR_PATH);
|
||||
ctx.restore();
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(
|
||||
pos.x,
|
||||
pos.y,
|
||||
size,
|
||||
flicker,
|
||||
rotation,
|
||||
0,
|
||||
2 * Math.PI,
|
||||
);
|
||||
ctx.fill();
|
||||
}
|
||||
});
|
||||
confettiRef.current = confettiRef.current.filter((c) => !confettiToRemove.includes(c));
|
||||
if (confettiRef.current.length) {
|
||||
|
||||
@ -248,6 +248,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
||||
loadSavedReactionTags,
|
||||
loadTimezones,
|
||||
loadQuickReplies,
|
||||
loadStarStatus,
|
||||
} = getActions();
|
||||
|
||||
if (DEBUG && !DEBUG_isLogged) {
|
||||
@ -328,6 +329,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
||||
loadSavedReactionTags();
|
||||
loadTimezones();
|
||||
loadQuickReplies();
|
||||
loadStarStatus();
|
||||
}
|
||||
}, [isMasterTab, isSynced]);
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import AvatarList from '../../common/AvatarList';
|
||||
import Icon from '../../common/Icon';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import Button from '../../ui/Button';
|
||||
import Link from '../../ui/Link';
|
||||
import Modal from '../../ui/Modal';
|
||||
@ -122,6 +122,7 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
left,
|
||||
width,
|
||||
height,
|
||||
withStars: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -31,7 +31,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import CalendarModal from '../../common/CalendarModal';
|
||||
import CountryPickerModal from '../../common/CountryPickerModal';
|
||||
import GroupChatInfo from '../../common/GroupChatInfo';
|
||||
import Icon from '../../common/Icon';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import Button from '../../ui/Button';
|
||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||
import InputText from '../../ui/InputText';
|
||||
|
||||
@ -7,7 +7,7 @@ import buildClassName from '../../../util/buildClassName';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Icon from '../../common/Icon';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
|
||||
import styles from './GiveawayTypeOption.module.scss';
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ import sortChatIds from '../../common/helpers/sortChatIds';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Icon from '../../common/Icon';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import Picker from '../../common/Picker';
|
||||
import Button from '../../ui/Button';
|
||||
import Modal from '../../ui/Modal';
|
||||
|
||||
@ -202,6 +202,7 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
||||
left,
|
||||
width,
|
||||
height,
|
||||
withStars: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -15,7 +15,7 @@ import renderText from '../../../common/helpers/renderText';
|
||||
import useFlag from '../../../../hooks/useFlag';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
|
||||
import Icon from '../../../common/Icon';
|
||||
import Icon from '../../../common/icons/Icon';
|
||||
import Button from '../../../ui/Button';
|
||||
import Modal from '../../../ui/Modal';
|
||||
import PremiumLimitsCompare from './PremiumLimitsCompare';
|
||||
|
||||
@ -102,7 +102,9 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
observeIntersectionForPlaying,
|
||||
onPinnedIntersectionChange,
|
||||
}) => {
|
||||
const { openPremiumModal, requestConfetti, checkGiftCode } = getActions();
|
||||
const {
|
||||
openPremiumModal, requestConfetti, checkGiftCode, getReceipt,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
@ -150,7 +152,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
useEffect(() => {
|
||||
if (isVisible && shouldShowConfettiRef.current) {
|
||||
shouldShowConfettiRef.current = false;
|
||||
requestConfetti({});
|
||||
requestConfetti({ withStars: true });
|
||||
}
|
||||
}, [isVisible, requestConfetti]);
|
||||
|
||||
@ -210,6 +212,15 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
checkGiftCode({ slug, message: { chatId: message.chatId, messageId: message.id } });
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
if (message.content.action?.type === 'receipt') {
|
||||
getReceipt({
|
||||
chatId: message.chatId,
|
||||
messageId: message.id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// TODO Refactoring for action rendering
|
||||
const shouldSkipRender = isInsideTopic && message.content.action?.text === 'TopicWasCreatedAction';
|
||||
if (shouldSkipRender) {
|
||||
@ -294,7 +305,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
{!isSuggestedAvatar && !isGiftCode && !isJoinedMessage && (
|
||||
<span className="action-message-content">{renderContent()}</span>
|
||||
<span className="action-message-content" onClick={handleClick}>{renderContent()}</span>
|
||||
)}
|
||||
{isGift && renderGift()}
|
||||
{isGiftCode && renderGiftCode()}
|
||||
|
||||
@ -132,6 +132,7 @@ type StateProps = {
|
||||
|
||||
const MESSAGE_REACTIONS_POLLING_INTERVAL = 20 * 1000;
|
||||
const MESSAGE_COMMENTS_POLLING_INTERVAL = 20 * 1000;
|
||||
const MESSAGE_FACT_CHECK_UPDATE_INTERVAL = 5 * 1000;
|
||||
const MESSAGE_STORY_POLLING_INTERVAL = 5 * 60 * 1000;
|
||||
const BOTTOM_THRESHOLD = 50;
|
||||
const UNREAD_DIVIDER_TOP = 10;
|
||||
@ -188,7 +189,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
}) => {
|
||||
const {
|
||||
loadViewportMessages, setScrollOffset, loadSponsoredMessages, loadMessageReactions, copyMessagesByIds,
|
||||
loadMessageViews, loadPeerStoriesByIds,
|
||||
loadMessageViews, loadPeerStoriesByIds, loadFactChecks,
|
||||
} = getActions();
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
@ -320,6 +321,17 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
loadMessageViews({ chatId, ids });
|
||||
}, MESSAGE_COMMENTS_POLLING_INTERVAL, true);
|
||||
|
||||
useInterval(() => {
|
||||
if (!messageIds || !messagesById || threadId !== MAIN_THREAD_ID || type === 'scheduled') {
|
||||
return;
|
||||
}
|
||||
const ids = messageIds.filter((id) => messagesById[id].factCheck?.shouldFetch);
|
||||
|
||||
if (!ids.length) return;
|
||||
|
||||
loadFactChecks({ chatId, ids });
|
||||
}, MESSAGE_FACT_CHECK_UPDATE_INTERVAL);
|
||||
|
||||
const loadMoreAround = useMemo(() => {
|
||||
if (type !== 'thread') {
|
||||
return undefined;
|
||||
|
||||
@ -10,7 +10,7 @@ import useLang from '../../hooks/useLang';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
|
||||
import AnimatedIconWithPreview from '../common/AnimatedIconWithPreview';
|
||||
import Icon from '../common/Icon';
|
||||
import Icon from '../common/icons/Icon';
|
||||
import Button from '../ui/Button';
|
||||
|
||||
import styles from './PremiumRequiredMessage.module.scss';
|
||||
|
||||
@ -30,7 +30,7 @@ import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useMouseInside from '../../../hooks/useMouseInside';
|
||||
|
||||
import Icon from '../../common/Icon';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import Menu from '../../ui/Menu';
|
||||
import MenuItem from '../../ui/MenuItem';
|
||||
import ResponsiveHoverButton from '../../ui/ResponsiveHoverButton';
|
||||
|
||||
@ -38,7 +38,7 @@ import useMenuPosition from '../../../hooks/useMenuPosition';
|
||||
import useShowTransition from '../../../hooks/useShowTransition';
|
||||
|
||||
import { ClosableEmbeddedMessage } from '../../common/embedded/EmbeddedMessage';
|
||||
import Icon from '../../common/Icon';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import Button from '../../ui/Button';
|
||||
import Menu from '../../ui/Menu';
|
||||
import MenuItem from '../../ui/MenuItem';
|
||||
|
||||
@ -1,21 +1,6 @@
|
||||
.root {
|
||||
position: relative;
|
||||
margin: 0.25rem 0.25rem 0.875rem 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
overflow: hidden;
|
||||
background-color: var(--accent-background-color);
|
||||
color: var(--accent-color);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
inset-inline-start: 0;
|
||||
width: 0.1875rem;
|
||||
background: var(--bar-gradient, var(--accent-color));
|
||||
}
|
||||
}
|
||||
|
||||
.info-container {
|
||||
|
||||
52
src/components/middle/message/FactCheck.module.scss
Normal file
52
src/components/middle/message/FactCheck.module.scss
Normal file
@ -0,0 +1,52 @@
|
||||
@use '../../../styles/mixins';
|
||||
|
||||
.root {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
padding-block: 0.25rem;
|
||||
padding-inline-end: 0.25rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.content {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.separator {
|
||||
--color-dividers: var(--accent-color);
|
||||
|
||||
margin-block: 0.25rem;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.footnote {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
@include mixins.gradient-border-bottom(1rem);
|
||||
}
|
||||
|
||||
.cutoutWrapper {
|
||||
max-height: inherit;
|
||||
}
|
||||
|
||||
.collapseIcon {
|
||||
position: absolute;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 50%;
|
||||
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
}
|
||||
89
src/components/middle/message/FactCheck.tsx
Normal file
89
src/components/middle/message/FactCheck.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
import React, { memo, useMemo, useRef } from '../../../lib/teact/teact';
|
||||
|
||||
import type { ApiFactCheck } from '../../../api/types';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
||||
|
||||
import useCollapsibleLines from '../../../hooks/element/useCollapsibleLines';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import PeerColorWrapper from '../../common/PeerColorWrapper';
|
||||
import Separator from '../../ui/Separator';
|
||||
|
||||
import styles from './FactCheck.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
factCheck: ApiFactCheck;
|
||||
isToggleDisabled?: boolean;
|
||||
};
|
||||
|
||||
const COLOR = {
|
||||
color: 0,
|
||||
};
|
||||
const MAX_LINES = 4;
|
||||
|
||||
const FactCheck = ({ factCheck, isToggleDisabled }: OwnProps) => {
|
||||
const lang = useLang();
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const cutoutRef = useRef<HTMLDivElement>(null);
|
||||
const {
|
||||
isCollapsed, isCollapsible, setIsCollapsed,
|
||||
} = useCollapsibleLines(ref, MAX_LINES, cutoutRef);
|
||||
|
||||
const countryLocalized = useMemo(() => {
|
||||
if (!factCheck.countryCode || !lang.code) return undefined;
|
||||
|
||||
const displayNames = new Intl.DisplayNames([lang.code], { type: 'region' });
|
||||
return displayNames.of(factCheck.countryCode);
|
||||
}, [factCheck.countryCode, lang.code]);
|
||||
|
||||
const canExpand = !isToggleDisabled && isCollapsed;
|
||||
|
||||
const handleExpand = useLastCallback(() => {
|
||||
setIsCollapsed(false);
|
||||
});
|
||||
|
||||
const handleToggle = useLastCallback(() => {
|
||||
setIsCollapsed((prev) => !prev);
|
||||
});
|
||||
|
||||
if (!factCheck.text) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<PeerColorWrapper peerColor={COLOR} className={styles.root} onClick={canExpand ? handleExpand : undefined}>
|
||||
<div
|
||||
ref={cutoutRef}
|
||||
className={buildClassName(styles.cutoutWrapper, isCollapsed && styles.collapsed)}
|
||||
>
|
||||
<div className={styles.title}>{lang('FactCheck')}</div>
|
||||
<div ref={ref} className={styles.content}>
|
||||
{renderTextWithEntities({
|
||||
text: factCheck.text.text,
|
||||
entities: factCheck.text.entities,
|
||||
})}
|
||||
</div>
|
||||
<Separator className={styles.separator} />
|
||||
<div className={styles.footnote}>{lang('FactCheckFooter', countryLocalized)}</div>
|
||||
</div>
|
||||
{isCollapsible && (
|
||||
<div
|
||||
className={buildClassName(styles.collapseIcon, !isToggleDisabled && styles.clickable)}
|
||||
onClick={!isToggleDisabled ? handleToggle : undefined}
|
||||
aria-hidden
|
||||
>
|
||||
<Icon name={isCollapsed ? 'down' : 'up'} />
|
||||
</div>
|
||||
)}
|
||||
</PeerColorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(FactCheck);
|
||||
@ -38,16 +38,16 @@
|
||||
top: 0;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin: 0.25rem;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
border-radius: var(--border-radius-messages-small);
|
||||
color: var(--color-white);
|
||||
font-weight: 500;
|
||||
|
||||
span {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.test-invoice {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.invoice-image-container {
|
||||
|
||||
@ -5,6 +5,7 @@ import type { ApiMessage } from '../../../api/types';
|
||||
import type { ISettings } from '../../../types';
|
||||
|
||||
import { CUSTOM_APPENDIX_ATTRIBUTE, MESSAGE_CONTENT_SELECTOR } from '../../../config';
|
||||
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
||||
import { getMessageInvoice, getWebDocumentHash } from '../../../global/helpers';
|
||||
import buildStyle from '../../../util/buildStyle';
|
||||
import { formatCurrency } from '../../../util/formatCurrency';
|
||||
@ -67,8 +68,10 @@ const Invoice: FC<OwnProps> = ({
|
||||
if (photoUrl) {
|
||||
const contentEl = ref.current!.closest<HTMLDivElement>(MESSAGE_CONTENT_SELECTOR)!;
|
||||
getCustomAppendixBg(photoUrl, false, isSelected, theme).then((appendixBg) => {
|
||||
contentEl.style.setProperty('--appendix-bg', appendixBg);
|
||||
contentEl.setAttribute(CUSTOM_APPENDIX_ATTRIBUTE, '');
|
||||
requestMutation(() => {
|
||||
contentEl.style.setProperty('--appendix-bg', appendixBg);
|
||||
contentEl.setAttribute(CUSTOM_APPENDIX_ATTRIBUTE, '');
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [shouldAffectAppendix, photoUrl, isInSelectMode, isSelected, theme]);
|
||||
@ -117,7 +120,7 @@ const Invoice: FC<OwnProps> = ({
|
||||
)}
|
||||
<p className="description-text">
|
||||
{formatCurrency(amount, currency, lang.code)}
|
||||
{isTest && <span>{lang('PaymentTestInvoice')}</span>}
|
||||
{isTest && <span className="test-invoice">{lang('PaymentTestInvoice')}</span>}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import type { TeactNode } from '../../../lib/teact/teact';
|
||||
import React from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
@ -12,19 +12,19 @@ import useAppLayout from '../../../hooks/useAppLayout';
|
||||
type OwnProps = {
|
||||
userId?: string;
|
||||
username?: string;
|
||||
children: React.ReactNode;
|
||||
children: TeactNode;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
userOrChat?: ApiPeer;
|
||||
};
|
||||
|
||||
const MentionLink: FC<OwnProps & StateProps> = ({
|
||||
const MentionLink = ({
|
||||
userId,
|
||||
username,
|
||||
userOrChat,
|
||||
children,
|
||||
}) => {
|
||||
}: OwnProps & StateProps) => {
|
||||
const {
|
||||
openChat,
|
||||
openChatByUsername,
|
||||
|
||||
@ -145,9 +145,9 @@ import DotAnimation from '../../common/DotAnimation';
|
||||
import EmbeddedMessage from '../../common/embedded/EmbeddedMessage';
|
||||
import EmbeddedStory from '../../common/embedded/EmbeddedStory';
|
||||
import FakeIcon from '../../common/FakeIcon';
|
||||
import Icon from '../../common/Icon';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import StarIcon from '../../common/icons/StarIcon';
|
||||
import MessageText from '../../common/MessageText';
|
||||
import PremiumIcon from '../../common/PremiumIcon';
|
||||
import ReactionStaticEmoji from '../../common/ReactionStaticEmoji';
|
||||
import TopicChip from '../../common/TopicChip';
|
||||
import Button from '../../ui/Button';
|
||||
@ -157,6 +157,7 @@ import AnimatedEmoji from './AnimatedEmoji';
|
||||
import CommentButton from './CommentButton';
|
||||
import Contact from './Contact';
|
||||
import ContextMenuContainer from './ContextMenuContainer.async';
|
||||
import FactCheck from './FactCheck';
|
||||
import Game from './Game';
|
||||
import Giveaway from './Giveaway';
|
||||
import InlineButtons from './InlineButtons';
|
||||
@ -470,7 +471,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
|
||||
const {
|
||||
id: messageId, chatId, forwardInfo, viaBotId, isTranscriptionError,
|
||||
id: messageId, chatId, forwardInfo, viaBotId, isTranscriptionError, factCheck,
|
||||
} = message;
|
||||
|
||||
useEffect(() => {
|
||||
@ -524,6 +525,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const noUserColors = isOwn && !isCustomShape;
|
||||
|
||||
const hasFactCheck = Boolean(factCheck?.text);
|
||||
|
||||
const hasSubheader = hasTopicChip || hasMessageReply || hasStoryReply;
|
||||
|
||||
const selectMessage = useLastCallback((e?: React.MouseEvent<HTMLDivElement, MouseEvent>, groupedId?: string) => {
|
||||
@ -627,12 +630,13 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
}, [focusLastMessage, isLastInList, transcribedText, withVoiceTranscription]);
|
||||
|
||||
const textMessage = album?.hasMultipleCaptions ? undefined : (album?.captionMessage || message);
|
||||
const hasText = textMessage && hasMessageText(textMessage);
|
||||
const hasTextContent = textMessage && hasMessageText(textMessage);
|
||||
const hasText = hasTextContent || hasFactCheck;
|
||||
|
||||
const containerClassName = buildClassName(
|
||||
'Message message-list-item',
|
||||
isFirstInGroup && 'first-in-group',
|
||||
isProtected && !hasText ? 'is-protected' : 'allow-selection',
|
||||
isProtected && !hasTextContent ? 'is-protected' : 'allow-selection',
|
||||
isLastInGroup && 'last-in-group',
|
||||
isFirstInDocumentGroup && 'first-in-document-group',
|
||||
isLastInDocumentGroup && 'last-in-document-group',
|
||||
@ -915,6 +919,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||
withTranslucentThumbs={isCustomShape}
|
||||
isInSelectMode={isInSelectMode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1227,6 +1232,9 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{hasFactCheck && (
|
||||
<FactCheck factCheck={factCheck} isToggleDisabled={isInSelectMode} />
|
||||
)}
|
||||
{metaPosition === 'in-text' && renderReactionsAndMeta()}
|
||||
</div>
|
||||
)}
|
||||
@ -1331,7 +1339,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||
/>
|
||||
)}
|
||||
{!asForwarded && !senderEmojiStatus && senderIsPremium && <PremiumIcon />}
|
||||
{!asForwarded && !senderEmojiStatus && senderIsPremium && <StarIcon />}
|
||||
{senderPeer?.fakeType && <FakeIcon fakeType={senderPeer.fakeType} />}
|
||||
</span>
|
||||
) : !botSender ? (
|
||||
|
||||
@ -31,7 +31,7 @@ import useShowTransition from '../../../hooks/useShowTransition';
|
||||
import useSignal from '../../../hooks/useSignal';
|
||||
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
|
||||
|
||||
import Icon from '../../common/Icon';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import MediaSpoiler from '../../common/MediaSpoiler';
|
||||
import Button from '../../ui/Button';
|
||||
import OptimizedVideo from '../../ui/OptimizedVideo';
|
||||
|
||||
@ -21,7 +21,7 @@ import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Avatar from '../../common/Avatar';
|
||||
import Icon from '../../common/Icon';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import Button from '../../ui/Button';
|
||||
import Skeleton from '../../ui/placeholder/Skeleton';
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import AboutAdsModal from '../../common/AboutAdsModal.async';
|
||||
import Avatar from '../../common/Avatar';
|
||||
import Icon from '../../common/Icon';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import PeerColorWrapper from '../../common/PeerColorWrapper';
|
||||
import Button from '../../ui/Button';
|
||||
import MessageAppendix from './MessageAppendix';
|
||||
|
||||
@ -322,7 +322,7 @@
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.PremiumIcon {
|
||||
.StarIcon {
|
||||
--color-fill: var(--accent-color);
|
||||
vertical-align: middle;
|
||||
opacity: 0.5;
|
||||
@ -963,12 +963,6 @@
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
// Remove extra bottom padding from `blockquote`
|
||||
.text-entity-blockquote-wrapper {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
blockquote, .blockquote {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
@ -40,10 +40,11 @@ export function buildContentClassName(
|
||||
giveaway, giveawayResults,
|
||||
} = getMessageContent(message);
|
||||
const text = album?.hasMultipleCaptions ? undefined : getMessageContent(album?.captionMessage || message).text;
|
||||
const hasFactCheck = Boolean(message.factCheck?.text);
|
||||
|
||||
const classNames = [MESSAGE_CONTENT_CLASS_NAME];
|
||||
const isMedia = storyData || photo || video || location || invoice?.extendedMedia;
|
||||
const hasText = text || location?.type === 'venue' || isGeoLiveActive;
|
||||
const hasText = text || location?.type === 'venue' || isGeoLiveActive || hasFactCheck;
|
||||
const isMediaWithNoText = isMedia && !hasText;
|
||||
const isViaBot = Boolean(message.viaBotId);
|
||||
|
||||
@ -144,7 +145,7 @@ export function buildContentClassName(
|
||||
classNames.push('has-background');
|
||||
}
|
||||
|
||||
if (hasSubheader || asForwarded || isViaBot || !isMediaWithNoText || forceSenderName) {
|
||||
if (hasSubheader || asForwarded || isViaBot || !isMediaWithNoText || forceSenderName || hasFactCheck) {
|
||||
classNames.push('has-solid-background');
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import buildClassName from '../../../../util/buildClassName';
|
||||
import { REM } from '../../../common/helpers/mediaDimensions';
|
||||
|
||||
import CustomEmoji from '../../../common/CustomEmoji';
|
||||
import Icon from '../../../common/Icon';
|
||||
import Icon from '../../../common/icons/Icon';
|
||||
|
||||
import styles from './ReactionSelectorReaction.module.scss';
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ import useFlag from '../../../../hooks/useFlag';
|
||||
import useMedia from '../../../../hooks/useMedia';
|
||||
|
||||
import AnimatedSticker from '../../../common/AnimatedSticker';
|
||||
import Icon from '../../../common/Icon';
|
||||
import Icon from '../../../common/icons/Icon';
|
||||
|
||||
import styles from './ReactionSelectorReaction.module.scss';
|
||||
|
||||
|
||||
@ -15,6 +15,8 @@ import InviteViaLinkModal from './inviteViaLink/InviteViaLinkModal.async';
|
||||
import MapModal from './map/MapModal.async';
|
||||
import OneTimeMediaModal from './oneTimeMedia/OneTimeMediaModal.async';
|
||||
import ReportAdModal from './reportAd/ReportAdModal.async';
|
||||
import StarsBalanceModal from './stars/StarsBalanceModal.async';
|
||||
import StarsPaymentModal from './stars/StarsPaymentModal.async';
|
||||
import UrlAuthModal from './urlAuth/UrlAuthModal.async';
|
||||
import WebAppModal from './webApp/WebAppModal.async';
|
||||
|
||||
@ -30,6 +32,8 @@ type ModalKey = keyof Pick<TabState,
|
||||
'requestedAttachBotInstall' |
|
||||
'collectibleInfoModal' |
|
||||
'reportAdModal' |
|
||||
'starsBalanceModal' |
|
||||
'isStarPaymentModalOpen' |
|
||||
'webApp'
|
||||
>;
|
||||
|
||||
@ -57,6 +61,8 @@ const MODALS: ModalRegistry = {
|
||||
webApp: WebAppModal,
|
||||
collectibleInfoModal: CollectibleInfoModal,
|
||||
mapModal: MapModal,
|
||||
isStarPaymentModalOpen: StarsPaymentModal,
|
||||
starsBalanceModal: StarsBalanceModal,
|
||||
};
|
||||
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
||||
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;
|
||||
|
||||
@ -17,7 +17,7 @@ import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Avatar from '../../common/Avatar';
|
||||
import Icon from '../../common/Icon';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import PremiumProgress from '../../common/PremiumProgress';
|
||||
import Button from '../../ui/Button';
|
||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||
|
||||
@ -20,7 +20,7 @@ import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
||||
import Icon from '../../common/Icon';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import PickerSelectedItem from '../../common/PickerSelectedItem';
|
||||
import Button from '../../ui/Button';
|
||||
import Modal from '../../ui/Modal';
|
||||
|
||||
37
src/components/modals/common/TableInfoModal.module.scss
Normal file
37
src/components/modals/common/TableInfoModal.module.scss
Normal file
@ -0,0 +1,37 @@
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
max-height: min(92vh, 40rem) !important;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: var(--color-background-secondary);
|
||||
}
|
||||
|
||||
.value {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.table .cell {
|
||||
border: 1px solid var(--color-borders);
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 6.25rem;
|
||||
height: 6.25rem;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.chatItem {
|
||||
margin: 0;
|
||||
width: fit-content;
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
100
src/components/modals/common/TableInfoModal.tsx
Normal file
100
src/components/modals/common/TableInfoModal.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
import React, { memo, type TeactNode } from '../../../lib/teact/teact';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type { ApiPeer, ApiWebDocument } from '../../../api/types';
|
||||
import type { CustomPeer } from '../../../types';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Avatar from '../../common/Avatar';
|
||||
import PickerSelectedItem from '../../common/PickerSelectedItem';
|
||||
import Button from '../../ui/Button';
|
||||
import Modal from '../../ui/Modal';
|
||||
|
||||
import styles from './TableInfoModal.module.scss';
|
||||
|
||||
type ChatItem = { chatId: string };
|
||||
|
||||
export type TableData = [TeactNode, TeactNode | ChatItem][];
|
||||
|
||||
type OwnProps = {
|
||||
isOpen?: boolean;
|
||||
title?: string;
|
||||
tableData?: TableData;
|
||||
headerImageUrl?: string;
|
||||
headerAvatarPeer?: ApiPeer | CustomPeer;
|
||||
headerAvatarWebPhoto?: ApiWebDocument;
|
||||
header?: TeactNode;
|
||||
footer?: TeactNode;
|
||||
buttonText?: string;
|
||||
onClose: NoneToVoidFunction;
|
||||
onButtonClick?: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
const TableInfoModal = ({
|
||||
isOpen,
|
||||
title,
|
||||
tableData,
|
||||
headerImageUrl,
|
||||
headerAvatarPeer,
|
||||
headerAvatarWebPhoto,
|
||||
header,
|
||||
footer,
|
||||
buttonText,
|
||||
onClose,
|
||||
onButtonClick,
|
||||
}: OwnProps) => {
|
||||
const { openChat } = getActions();
|
||||
const handleOpenChat = useLastCallback((peerId: string) => {
|
||||
openChat({ id: peerId });
|
||||
onClose();
|
||||
});
|
||||
|
||||
const withAvatar = Boolean(headerAvatarPeer || headerAvatarWebPhoto);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
hasCloseButton={Boolean(title)}
|
||||
hasAbsoluteCloseButton={!title}
|
||||
isSlim
|
||||
title={title}
|
||||
contentClassName={styles.content}
|
||||
onClose={onClose}
|
||||
>
|
||||
{withAvatar ? (
|
||||
<Avatar peer={headerAvatarPeer} webPhoto={headerAvatarWebPhoto} size="jumbo" className={styles.avatar} />
|
||||
) : (
|
||||
<img className={styles.logo} src={headerImageUrl} alt="" draggable={false} />
|
||||
)}
|
||||
{header}
|
||||
<table className={styles.table}>
|
||||
{tableData?.map(([label, value]) => (
|
||||
<tr className={styles.row}>
|
||||
<td className={buildClassName(styles.cell, styles.title)}>{label}</td>
|
||||
<td className={buildClassName(styles.cell, styles.value)}>
|
||||
{typeof value === 'object' && 'chatId' in value ? (
|
||||
<PickerSelectedItem
|
||||
peerId={value.chatId}
|
||||
className={styles.chatItem}
|
||||
forceShowSelf
|
||||
fluid
|
||||
clickArg={value.chatId}
|
||||
onClick={handleOpenChat}
|
||||
/>
|
||||
) : value}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</table>
|
||||
{footer}
|
||||
{buttonText && (
|
||||
<Button onClick={onButtonClick || onClose}>{buttonText}</Button>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(TableInfoModal);
|
||||
@ -1,38 +1,8 @@
|
||||
.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;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { memo } from '../../../lib/teact/teact';
|
||||
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiPeer } from '../../../api/types';
|
||||
@ -14,9 +14,7 @@ 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 TableInfoModal, { type TableData } from '../common/TableInfoModal';
|
||||
|
||||
import styles from './GiftCodeModal.module.scss';
|
||||
|
||||
@ -39,18 +37,13 @@ const GiftCodeModal = ({
|
||||
messageSender,
|
||||
}: OwnProps & StateProps) => {
|
||||
const {
|
||||
closeGiftCodeModal, openChat, applyGiftCode, focusMessage,
|
||||
closeGiftCodeModal, 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({
|
||||
@ -68,101 +61,63 @@ const GiftCodeModal = ({
|
||||
closeGiftCodeModal();
|
||||
});
|
||||
|
||||
function renderContent() {
|
||||
const modalData = useMemo(() => {
|
||||
if (!modal) return undefined;
|
||||
const { slug, info } = modal;
|
||||
|
||||
const fromId = info.fromId || messageSender?.id;
|
||||
|
||||
return (
|
||||
const header = (
|
||||
<>
|
||||
<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>
|
||||
{fromId ? (
|
||||
<PickerSelectedItem
|
||||
peerId={fromId}
|
||||
className={styles.chatItem}
|
||||
forceShowSelf
|
||||
fluid
|
||||
clickArg={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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const tableData = [
|
||||
[lang('BoostingFrom'), fromId ? { chatId: fromId } : lang('BoostingNoRecipient')],
|
||||
[lang('BoostingTo'), info.toId ? { chatId: info.toId } : lang('BoostingNoRecipient')],
|
||||
[lang('BoostingGift'), lang('BoostingTelegramPremiumFor', lang('Months', info.months, 'i'))],
|
||||
[lang('BoostingReason'), (
|
||||
<span className={buildClassName(info.giveawayMessageId && styles.clickable)} onClick={handleOpenGiveaway}>
|
||||
{info.isFromGiveaway && !info.toId ? lang('BoostingIncompleteGiveaway')
|
||||
: lang(info.isFromGiveaway ? 'BoostingGiveaway' : 'BoostingYouWereSelected')}
|
||||
</span>
|
||||
)],
|
||||
[lang('BoostingDate'), formatDateTimeToString(info.date * 1000, lang.code, true)],
|
||||
] satisfies TableData;
|
||||
|
||||
const footer = (
|
||||
<span className={styles.centered}>
|
||||
{renderText(
|
||||
info.usedAt ? lang('BoostingUsedLinkDate', formatDateTimeToString(info.usedAt * 1000, lang.code, true))
|
||||
: lang('BoostingSendLinkToAnyone'),
|
||||
['simple_markdown'],
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
|
||||
return {
|
||||
header,
|
||||
tableData,
|
||||
footer,
|
||||
};
|
||||
}, [lang, messageSender?.id, modal]);
|
||||
|
||||
if (!modalData) return undefined;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
<TableInfoModal
|
||||
isOpen={isOpen}
|
||||
hasCloseButton
|
||||
isSlim
|
||||
title={lang('lng_gift_link_title')}
|
||||
contentClassName={styles.content}
|
||||
headerImageUrl={PremiumLogo}
|
||||
tableData={modalData.tableData}
|
||||
header={modalData.header}
|
||||
footer={modalData.footer}
|
||||
buttonText={canUse ? lang('BoostingUseLink') : lang('Close')}
|
||||
onButtonClick={handleButtonClick}
|
||||
onClose={closeGiftCodeModal}
|
||||
>
|
||||
{renderContent()}
|
||||
</Modal>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import buildClassName from '../../../util/buildClassName';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Icon from '../../common/Icon';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import SafeLink from '../../common/SafeLink';
|
||||
import Button from '../../ui/Button';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
|
||||
31
src/components/modals/stars/BalanceBlock.tsx
Normal file
31
src/components/modals/stars/BalanceBlock.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React, { memo } from '../../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatInteger } from '../../../util/textFormat';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import StarIcon from '../../common/icons/StarIcon';
|
||||
|
||||
import styles from './StarsBalanceModal.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
balance: number;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const BalanceBlock = ({ balance, className }: OwnProps) => {
|
||||
const lang = useLang();
|
||||
|
||||
return (
|
||||
<div className={buildClassName(styles.balance, className)}>
|
||||
<span className={styles.smallerText}>{lang('StarsBalance')}</span>
|
||||
<div className={styles.balanceBottom}>
|
||||
<StarIcon type="gold" size="middle" />
|
||||
{formatInteger(balance)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(BalanceBlock);
|
||||
18
src/components/modals/stars/StarsBalanceModal.async.tsx
Normal file
18
src/components/modals/stars/StarsBalanceModal.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 './StarsBalanceModal';
|
||||
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const StarsBalanceModalAsync: FC<OwnProps> = (props) => {
|
||||
const { modal } = props;
|
||||
const StarsBalanceModal = useModuleLoader(Bundles.Extra, 'StarsBalanceModal', !modal);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return StarsBalanceModal ? <StarsBalanceModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default StarsBalanceModalAsync;
|
||||
239
src/components/modals/stars/StarsBalanceModal.module.scss
Normal file
239
src/components/modals/stars/StarsBalanceModal.module.scss
Normal file
@ -0,0 +1,239 @@
|
||||
@use '../../../styles/mixins';
|
||||
|
||||
.root :global(.modal-content) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.root :global(.modal-dialog) {
|
||||
height: min(calc(55vh + 41px + 193px), 90vh);
|
||||
max-width: 26.25rem;
|
||||
}
|
||||
|
||||
.root :global(.modal-dialog),
|
||||
.root :global(.modal-content),
|
||||
.transition {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main {
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
padding: 0.5rem;
|
||||
position: relative;
|
||||
|
||||
@include mixins.adapt-padding-to-scrollbar(0.5rem);
|
||||
|
||||
@include mixins.side-panel-section;
|
||||
}
|
||||
|
||||
.secondaryInfo {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
background-color: var(--color-background-secondary);
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 1rem;
|
||||
width: 6.25rem;
|
||||
height: 6.25rem;
|
||||
min-height: 6.25rem;
|
||||
}
|
||||
|
||||
.logoBackground {
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
height: 8rem;
|
||||
}
|
||||
|
||||
.headerHext {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
margin-inline: 0.5rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
text-align: center;
|
||||
margin-inline: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.header {
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 0.0625rem solid var(--color-borders);
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 3.5rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--color-background);
|
||||
transition: 0.25s ease-out transform;
|
||||
}
|
||||
|
||||
.starHeaderText {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
margin: 0 0 0 3rem;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
.hiddenHeader {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 0.5rem;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.balance {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.smallerText {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.balanceBottom {
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.modalBalance {
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
right: 1.25rem;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.options {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.option {
|
||||
--_background-color: var(--color-background-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.125rem;
|
||||
|
||||
padding: 1rem;
|
||||
border-radius: 0.625rem;
|
||||
|
||||
background-color: var(--_background-color);
|
||||
transition: background-color 0.25s ease-out;
|
||||
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
&:hover {
|
||||
--_background-color: var(--color-background-secondary-accent);
|
||||
}
|
||||
}
|
||||
|
||||
.optionTop {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stackedStars {
|
||||
display: grid;
|
||||
grid-auto-columns: 0.4375rem;
|
||||
grid-auto-flow: column;
|
||||
justify-items: end;
|
||||
}
|
||||
|
||||
.stackedStar {
|
||||
@include mixins.filter-outline(0.0625rem, var(--_background-color));
|
||||
}
|
||||
|
||||
.optionBottom {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.moreOptions {
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
.iconDown {
|
||||
margin-inline-start: 0.25rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.paymentContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.paymentImages {
|
||||
display: grid;
|
||||
grid-auto-columns: 3.5rem;
|
||||
grid-auto-flow: column;
|
||||
place-items: center;
|
||||
margin-bottom: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.paymentPhoto {
|
||||
outline: 0.25rem solid var(--color-background);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.paymentImageBackground {
|
||||
height: 7rem;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.paymentAmount {
|
||||
display: flex;
|
||||
line-height: 1.125;
|
||||
gap: 0.125rem;
|
||||
}
|
||||
|
||||
.paymentButton {
|
||||
display: flex;
|
||||
gap: 0.125rem;
|
||||
}
|
||||
|
||||
.paymentButtonStar {
|
||||
--color-fill: white !important;
|
||||
}
|
||||
|
||||
.transactions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user