Support TON balance (#6076)
Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
This commit is contained in:
parent
58d2906425
commit
a0e8eff8e3
@ -12,6 +12,10 @@ import {
|
||||
TODO_ITEM_LENGTH_LIMIT,
|
||||
TODO_ITEMS_LIMIT,
|
||||
TODO_TITLE_LENGTH_LIMIT,
|
||||
TON_SUGGESTED_POST_AMOUNT_MAX,
|
||||
TON_SUGGESTED_POST_AMOUNT_MIN,
|
||||
TON_TOPUP_URL_DEFAULT,
|
||||
TON_USD_RATE_DEFAULT,
|
||||
} from '../../../config';
|
||||
import localDb from '../localDb';
|
||||
import { buildJson } from './misc';
|
||||
@ -108,6 +112,10 @@ export interface GramJsAppConfig extends LimitsConfig {
|
||||
stars_suggested_post_future_max?: number;
|
||||
stars_suggested_post_future_min?: number;
|
||||
ton_suggested_post_commission_permille?: number;
|
||||
ton_suggested_post_amount_max?: number;
|
||||
ton_suggested_post_amount_min?: number;
|
||||
ton_usd_rate?: number;
|
||||
ton_topup_url?: string;
|
||||
poll_answers_max?: number;
|
||||
todo_items_max?: number;
|
||||
todo_title_length_max?: number;
|
||||
@ -219,6 +227,10 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
||||
starsSuggestedPostFutureMax: appConfig.stars_suggested_post_future_max,
|
||||
starsSuggestedPostFutureMin: appConfig.stars_suggested_post_future_min,
|
||||
tonSuggestedPostCommissionPermille: appConfig.ton_suggested_post_commission_permille,
|
||||
tonSuggestedPostAmountMax: appConfig.ton_suggested_post_amount_max ?? TON_SUGGESTED_POST_AMOUNT_MAX,
|
||||
tonSuggestedPostAmountMin: appConfig.ton_suggested_post_amount_min ?? TON_SUGGESTED_POST_AMOUNT_MIN,
|
||||
tonUsdRate: appConfig.ton_usd_rate ?? TON_USD_RATE_DEFAULT,
|
||||
tonTopupUrl: appConfig.ton_topup_url ?? TON_TOPUP_URL_DEFAULT,
|
||||
pollMaxAnswers: appConfig.poll_answers_max,
|
||||
todoItemsMax: appConfig.todo_items_max ?? TODO_ITEMS_LIMIT,
|
||||
todoTitleLengthMax: appConfig.todo_title_length_max ?? TODO_TITLE_LENGTH_LIMIT,
|
||||
|
||||
@ -7,7 +7,7 @@ import { buildApiBotApp } from './bots';
|
||||
import { buildApiFormattedText, buildApiPhoto } from './common';
|
||||
import { buildApiStarGift } from './gifts';
|
||||
import { buildTodoItem } from './messageContent';
|
||||
import { buildApiStarsAmount } from './payments';
|
||||
import { buildApiCurrencyAmount } from './payments';
|
||||
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
||||
|
||||
const UNSUPPORTED_ACTION: ApiMessageAction = {
|
||||
@ -359,6 +359,20 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
||||
transactionId,
|
||||
};
|
||||
}
|
||||
if (action instanceof GramJs.MessageActionGiftTon) {
|
||||
const {
|
||||
currency, amount, cryptoCurrency, cryptoAmount, transactionId,
|
||||
} = action;
|
||||
return {
|
||||
mediaType: 'action',
|
||||
type: 'giftTon',
|
||||
currency,
|
||||
amount: amount.toJSNumber(),
|
||||
cryptoCurrency,
|
||||
cryptoAmount: cryptoAmount.toJSNumber(),
|
||||
transactionId,
|
||||
};
|
||||
}
|
||||
if (action instanceof GramJs.MessageActionPrizeStars) {
|
||||
const {
|
||||
unclaimed, stars, transactionId, boostPeer, giveawayMsgId,
|
||||
@ -459,7 +473,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
||||
isBalanceTooLow: Boolean(balanceTooLow),
|
||||
rejectComment,
|
||||
scheduleDate,
|
||||
amount: price ? buildApiStarsAmount(price) : undefined,
|
||||
amount: price ? buildApiCurrencyAmount(price) : undefined,
|
||||
};
|
||||
}
|
||||
if (action instanceof GramJs.MessageActionSuggestedPostSuccess) {
|
||||
@ -467,7 +481,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
||||
return {
|
||||
mediaType: 'action',
|
||||
type: 'suggestedPostSuccess',
|
||||
amount: buildApiStarsAmount(price),
|
||||
amount: buildApiCurrencyAmount(price),
|
||||
};
|
||||
}
|
||||
if (action instanceof GramJs.MessageActionSuggestedPostRefund) {
|
||||
|
||||
@ -47,7 +47,7 @@ import { omitUndefined, pick } from '../../../util/iteratees';
|
||||
import { getServerTime, getServerTimeOffset } from '../../../util/serverTime';
|
||||
import { interpolateArray } from '../../../util/waveform';
|
||||
import {
|
||||
buildApiStarsAmount,
|
||||
buildApiCurrencyAmount,
|
||||
} from '../apiBuilders/payments';
|
||||
import { buildPeer } from '../gramjsBuilders';
|
||||
import {
|
||||
@ -302,7 +302,7 @@ export function buildMessageDraft(draft: GramJs.TypeDraftMessage): ApiDraft | un
|
||||
const suggestedPostInfo = suggestedPost instanceof GramJs.SuggestedPost ? {
|
||||
isAccepted: suggestedPost.accepted,
|
||||
isRejected: suggestedPost.rejected,
|
||||
price: suggestedPost.price ? buildApiStarsAmount(suggestedPost.price) : undefined,
|
||||
price: suggestedPost.price ? buildApiCurrencyAmount(suggestedPost.price) : undefined,
|
||||
scheduleDate: suggestedPost.scheduleDate,
|
||||
} satisfies ApiInputSuggestedPostInfo : undefined;
|
||||
|
||||
@ -319,7 +319,7 @@ function buildApiSuggestedPost(suggestedPost: GramJs.SuggestedPost): ApiSuggeste
|
||||
return {
|
||||
isAccepted: suggestedPost.accepted,
|
||||
isRejected: suggestedPost.rejected,
|
||||
price: suggestedPost.price ? buildApiStarsAmount(suggestedPost.price) : undefined,
|
||||
price: suggestedPost.price ? buildApiCurrencyAmount(suggestedPost.price) : undefined,
|
||||
scheduleDate: suggestedPost.scheduleDate,
|
||||
};
|
||||
}
|
||||
|
||||
@ -20,15 +20,16 @@ import type {
|
||||
ApiPrepaidStarsGiveaway,
|
||||
ApiReceipt,
|
||||
ApiStarGiveawayOption,
|
||||
ApiStarsAmount,
|
||||
ApiStarsGiveawayWinnerOption,
|
||||
ApiStarsSubscription,
|
||||
ApiStarsTransaction,
|
||||
ApiStarsTransactionPeer,
|
||||
ApiStarTopupOption,
|
||||
ApiTypeCurrencyAmount,
|
||||
BoughtPaidMedia,
|
||||
} from '../../types';
|
||||
|
||||
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../config';
|
||||
import { addWebDocumentToLocalDb } from '../helpers/localDb';
|
||||
import { buildApiStarsSubscriptionPricing } from './chats';
|
||||
import { buildApiMessageEntity } from './common';
|
||||
@ -461,28 +462,25 @@ export function buildApiStarsGiftOptions(option: GramJs.StarsGiftOption): ApiSta
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiStarsAmount(amount: GramJs.TypeStarsAmount): ApiStarsAmount | undefined {
|
||||
export function buildApiCurrencyAmount(amount: GramJs.TypeStarsAmount): ApiTypeCurrencyAmount | undefined {
|
||||
if (amount instanceof GramJs.StarsAmount) {
|
||||
return {
|
||||
currency: STARS_CURRENCY_CODE,
|
||||
amount: amount.amount.toJSNumber(),
|
||||
nanos: amount.nanos,
|
||||
};
|
||||
}
|
||||
|
||||
if (amount instanceof GramJs.StarsTonAmount) {
|
||||
return undefined;
|
||||
return {
|
||||
currency: TON_CURRENCY_CODE,
|
||||
amount: amount.amount.toJSNumber(),
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function buildInputStarsAmount(amount: ApiStarsAmount): GramJs.TypeStarsAmount {
|
||||
return new GramJs.StarsAmount({
|
||||
amount: bigInt(amount.amount),
|
||||
nanos: amount.nanos,
|
||||
});
|
||||
}
|
||||
|
||||
export function buildApiStarsGiveawayWinnersOption(
|
||||
option: GramJs.StarsGiveawayWinnersOption,
|
||||
): ApiStarsGiveawayWinnerOption {
|
||||
@ -563,7 +561,7 @@ export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction):
|
||||
|
||||
const starRefCommision = starrefCommissionPermille ? starrefCommissionPermille / 10 : undefined;
|
||||
|
||||
const starsAmount = buildApiStarsAmount(amount);
|
||||
const starsAmount = buildApiCurrencyAmount(amount);
|
||||
if (!starsAmount) {
|
||||
return undefined;
|
||||
}
|
||||
@ -572,7 +570,7 @@ export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction):
|
||||
id,
|
||||
date,
|
||||
peer: buildApiStarsTransactionPeer(peer),
|
||||
stars: starsAmount,
|
||||
amount: starsAmount,
|
||||
title,
|
||||
description,
|
||||
photo: photo && buildApiWebDocument(photo),
|
||||
|
||||
@ -33,15 +33,15 @@ import type {
|
||||
ApiStory,
|
||||
ApiStorySkipped,
|
||||
ApiThemeParameters,
|
||||
ApiTypeCurrencyAmount,
|
||||
ApiVideo,
|
||||
} from '../../types';
|
||||
import {
|
||||
ApiMessageEntityTypes,
|
||||
} from '../../types';
|
||||
|
||||
import { CHANNEL_ID_BASE, DEFAULT_STATUS_ICON_ID } from '../../../config';
|
||||
import { CHANNEL_ID_BASE, DEFAULT_STATUS_ICON_ID, STARS_CURRENCY_CODE } from '../../../config';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import { buildInputStarsAmount } from '../apiBuilders/payments';
|
||||
import { deserializeBytes } from '../helpers/misc';
|
||||
import localDb from '../localDb';
|
||||
|
||||
@ -890,12 +890,22 @@ export function buildInputReplyTo(replyInfo: ApiInputReplyInfo) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function buildInputSuggestedPost(suggestedPostInfo: ApiInputSuggestedPostInfo): GramJs.SuggestedPost {
|
||||
const isPaid = Boolean(suggestedPostInfo.price)
|
||||
&& Boolean((suggestedPostInfo.price.amount || suggestedPostInfo.price.nanos));
|
||||
export function buildInputStarsAmount(amount: ApiTypeCurrencyAmount): GramJs.TypeStarsAmount {
|
||||
if (amount.currency === STARS_CURRENCY_CODE) {
|
||||
return new GramJs.StarsAmount({
|
||||
amount: BigInt(amount.amount),
|
||||
nanos: amount.nanos,
|
||||
});
|
||||
}
|
||||
|
||||
return new GramJs.StarsTonAmount({
|
||||
amount: BigInt(amount.amount),
|
||||
});
|
||||
}
|
||||
|
||||
export function buildInputSuggestedPost(suggestedPostInfo: ApiInputSuggestedPostInfo): GramJs.SuggestedPost {
|
||||
return new GramJs.SuggestedPost({
|
||||
price: isPaid ? buildInputStarsAmount(suggestedPostInfo.price!) : undefined,
|
||||
price: suggestedPostInfo.price && buildInputStarsAmount(suggestedPostInfo.price),
|
||||
scheduleDate: suggestedPostInfo.scheduleDate,
|
||||
});
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import { buildApiResaleGifts, buildApiSavedStarGift, buildApiStarGift,
|
||||
buildApiStarGiftAttribute, buildInputResaleGiftsAttributes } from '../apiBuilders/gifts';
|
||||
import {
|
||||
buildApiStarsAmount,
|
||||
buildApiCurrencyAmount,
|
||||
buildApiStarsGiftOptions,
|
||||
buildApiStarsGiveawayOptions,
|
||||
buildApiStarsSubscription,
|
||||
@ -182,18 +182,22 @@ export async function getStarsGiftOptions({
|
||||
return result.map(buildApiStarsGiftOptions);
|
||||
}
|
||||
|
||||
export async function fetchStarsStatus() {
|
||||
export async function fetchStarsStatus({
|
||||
isTon,
|
||||
}: {
|
||||
isTon?: boolean;
|
||||
} = {}) {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsStatus({
|
||||
peer: new GramJs.InputPeerSelf(),
|
||||
ton: isTon || undefined,
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const balance = buildApiStarsAmount(result.balance);
|
||||
const balance = buildApiCurrencyAmount(result.balance);
|
||||
if (!balance) {
|
||||
// For now, skip if balance is in TON
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -212,29 +216,31 @@ export async function fetchStarsTransactions({
|
||||
limit = DEFAULT_PRIMITIVES.INT,
|
||||
isInbound,
|
||||
isOutbound,
|
||||
isTon,
|
||||
}: {
|
||||
peer?: ApiPeer;
|
||||
offset?: string;
|
||||
limit?: number;
|
||||
isInbound?: true;
|
||||
isOutbound?: true;
|
||||
isInbound?: boolean;
|
||||
isOutbound?: boolean;
|
||||
isTon?: boolean;
|
||||
}) {
|
||||
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsTransactions({
|
||||
peer: inputPeer,
|
||||
offset,
|
||||
limit,
|
||||
inbound: isInbound,
|
||||
outbound: isOutbound,
|
||||
inbound: isInbound || undefined,
|
||||
outbound: isOutbound || undefined,
|
||||
ton: isTon || undefined,
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const balance = buildApiStarsAmount(result.balance);
|
||||
const balance = buildApiCurrencyAmount(result.balance);
|
||||
if (!balance) {
|
||||
// For now, skip if balance is in TON
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -246,14 +252,16 @@ export async function fetchStarsTransactions({
|
||||
}
|
||||
|
||||
export async function fetchStarsTransactionById({
|
||||
id, peer,
|
||||
id, peer, ton,
|
||||
}: {
|
||||
id: string;
|
||||
peer?: ApiPeer;
|
||||
ton?: true;
|
||||
}) {
|
||||
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsTransactionsByID({
|
||||
peer: inputPeer,
|
||||
ton,
|
||||
id: [new GramJs.InputStarsTransaction({
|
||||
id,
|
||||
})],
|
||||
@ -285,9 +293,8 @@ export async function fetchStarsSubscriptions({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const balance = buildApiStarsAmount(result.balance);
|
||||
const balance = buildApiCurrencyAmount(result.balance);
|
||||
if (!balance) {
|
||||
// For now, skip if balance is in TON
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@ -272,6 +272,22 @@ export async function fetchPremiumGifts() {
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchTonGifts() {
|
||||
const result = await invokeRequest(new GramJs.messages.GetStickerSet({
|
||||
stickerset: new GramJs.InputStickerSetTonGifts(),
|
||||
hash: DEFAULT_PRIMITIVES.INT,
|
||||
}));
|
||||
|
||||
if (!(result instanceof GramJs.messages.StickerSet)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
set: buildStickerSet(result.set),
|
||||
stickers: processStickerResult(result.documents),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchDefaultTopicIcons() {
|
||||
const result = await invokeRequest(new GramJs.messages.GetStickerSet({
|
||||
stickerset: new GramJs.InputStickerSetEmojiDefaultTopicIcons(),
|
||||
|
||||
@ -50,7 +50,7 @@ import {
|
||||
buildLangStrings,
|
||||
buildPrivacyKey,
|
||||
} from '../apiBuilders/misc';
|
||||
import { buildApiStarsAmount } from '../apiBuilders/payments';
|
||||
import { buildApiCurrencyAmount } from '../apiBuilders/payments';
|
||||
import { buildApiEmojiStatus, buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
||||
import {
|
||||
buildApiPaidReactionPrivacy,
|
||||
@ -1042,7 +1042,7 @@ export function updater(update: Update) {
|
||||
isEnabled: update.enabled ? true : undefined,
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdateStarsBalance) {
|
||||
const balance = buildApiStarsAmount(update.balance);
|
||||
const balance = buildApiCurrencyAmount(update.balance);
|
||||
if (!balance) {
|
||||
// Skip TON balance updates for now
|
||||
return;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { ApiGroupCall, ApiPhoneCallDiscardReason } from './calls';
|
||||
import type { ApiBotApp, ApiFormattedText, ApiPhoto } from './messages';
|
||||
import type { ApiTodoItem } from './messages';
|
||||
import type { ApiStarGiftRegular, ApiStarGiftUnique, ApiStarsAmount } from './stars';
|
||||
import type { ApiStarGiftRegular, ApiStarGiftUnique, ApiTypeCurrencyAmount } from './stars';
|
||||
|
||||
interface ActionMediaType {
|
||||
mediaType: 'action';
|
||||
@ -216,6 +216,15 @@ export interface ApiMessageActionGiftStars extends ActionMediaType {
|
||||
transactionId?: string;
|
||||
}
|
||||
|
||||
export interface ApiMessageActionGiftTon extends ActionMediaType {
|
||||
type: 'giftTon';
|
||||
currency: string;
|
||||
amount: number;
|
||||
cryptoCurrency: string;
|
||||
cryptoAmount: number;
|
||||
transactionId?: string;
|
||||
}
|
||||
|
||||
export interface ApiMessageActionPrizeStars extends ActionMediaType {
|
||||
type: 'prizeStars';
|
||||
isUnclaimed?: true;
|
||||
@ -288,12 +297,12 @@ export interface ApiMessageActionSuggestedPostApproval extends ActionMediaType {
|
||||
isBalanceTooLow?: boolean;
|
||||
rejectComment?: string;
|
||||
scheduleDate?: number;
|
||||
amount?: ApiStarsAmount;
|
||||
amount?: ApiTypeCurrencyAmount;
|
||||
}
|
||||
|
||||
export interface ApiMessageActionSuggestedPostSuccess extends ActionMediaType {
|
||||
type: 'suggestedPostSuccess';
|
||||
amount?: ApiStarsAmount;
|
||||
amount?: ApiTypeCurrencyAmount;
|
||||
}
|
||||
|
||||
export interface ApiMessageActionSuggestedPostRefund extends ActionMediaType {
|
||||
@ -328,7 +337,7 @@ export type ApiMessageAction = ApiMessageActionUnsupported | ApiMessageActionCha
|
||||
| ApiMessageActionTopicCreate | ApiMessageActionTopicEdit | ApiMessageActionSuggestProfilePhoto
|
||||
| ApiMessageActionChannelJoined | ApiMessageActionGiftCode | ApiMessageActionGiveawayLaunch
|
||||
| ApiMessageActionGiveawayResults | ApiMessageActionPaymentRefunded | ApiMessageActionGiftStars
|
||||
| ApiMessageActionPrizeStars | ApiMessageActionStarGift | ApiMessageActionStarGiftUnique
|
||||
| ApiMessageActionGiftTon | ApiMessageActionPrizeStars | ApiMessageActionStarGift | ApiMessageActionStarGiftUnique
|
||||
| ApiMessageActionPaidMessagesRefunded | ApiMessageActionPaidMessagesPrice | ApiMessageActionSuggestedPostApproval
|
||||
| ApiMessageActionSuggestedPostSuccess | ApiMessageActionSuggestedPostRefund | ApiMessageActionTodoCompletions
|
||||
| ApiMessageActionTodoAppendTasks;
|
||||
|
||||
@ -9,7 +9,7 @@ import type { ApiMessageAction } from './messageActions';
|
||||
import type {
|
||||
ApiLabeledPrice,
|
||||
} from './payments';
|
||||
import type { ApiStarGiftUnique, ApiStarsAmount } from './stars';
|
||||
import type { ApiStarGiftUnique, ApiTypeCurrencyAmount } from './stars';
|
||||
import type {
|
||||
ApiMessageStoryData, ApiStory, ApiWebPageStickerData, ApiWebPageStoryData,
|
||||
} from './stories';
|
||||
@ -415,7 +415,7 @@ export interface ApiInputMessageReplyInfo {
|
||||
export interface ApiSuggestedPost {
|
||||
isAccepted?: true;
|
||||
isRejected?: true;
|
||||
price?: ApiStarsAmount;
|
||||
price?: ApiTypeCurrencyAmount;
|
||||
scheduleDate?: number;
|
||||
}
|
||||
|
||||
@ -426,7 +426,7 @@ export interface ApiInputStoryReplyInfo {
|
||||
}
|
||||
|
||||
export interface ApiInputSuggestedPostInfo {
|
||||
price?: ApiStarsAmount;
|
||||
price?: ApiTypeCurrencyAmount;
|
||||
scheduleDate?: number;
|
||||
isAccepted?: true;
|
||||
isRejected?: true;
|
||||
|
||||
@ -259,6 +259,10 @@ export interface ApiAppConfig {
|
||||
starsSuggestedPostFutureMax?: number;
|
||||
starsSuggestedPostFutureMin?: number;
|
||||
tonSuggestedPostCommissionPermille?: number;
|
||||
tonSuggestedPostAmountMax?: number;
|
||||
tonSuggestedPostAmountMin?: number;
|
||||
tonUsdRate?: number;
|
||||
tonTopupUrl?: string;
|
||||
pollMaxAnswers?: number;
|
||||
todoItemsMax?: number;
|
||||
todoTitleLengthMax?: number;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../config';
|
||||
import type { ApiWebDocument } from './bots';
|
||||
import type { ApiChat } from './chats';
|
||||
import type { ApiFormattedText, ApiSticker, BoughtPaidMedia } from './messages';
|
||||
@ -153,11 +154,19 @@ export type ApiRequestInputSavedStarGiftChat = {
|
||||
};
|
||||
export type ApiRequestInputSavedStarGift = ApiRequestInputSavedStarGiftUser | ApiRequestInputSavedStarGiftChat;
|
||||
|
||||
export type ApiTypeCurrencyAmount = ApiStarsAmount | ApiTonAmount;
|
||||
|
||||
export interface ApiStarsAmount {
|
||||
currency: typeof STARS_CURRENCY_CODE;
|
||||
amount: number;
|
||||
nanos: number;
|
||||
}
|
||||
|
||||
export interface ApiTonAmount {
|
||||
currency: typeof TON_CURRENCY_CODE;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export interface ApiStarsTransactionPeerUnsupported {
|
||||
type: 'unsupported';
|
||||
}
|
||||
@ -205,7 +214,7 @@ export interface ApiStarsTransaction {
|
||||
id?: string;
|
||||
peer: ApiStarsTransactionPeer;
|
||||
messageId?: number;
|
||||
stars: ApiStarsAmount;
|
||||
amount: ApiTypeCurrencyAmount;
|
||||
isRefund?: true;
|
||||
isGift?: true;
|
||||
starGift?: ApiStarGift;
|
||||
|
||||
@ -43,7 +43,7 @@ import type {
|
||||
ApiSessionData,
|
||||
} from './misc';
|
||||
import type { ApiPrivacyKey, LangPackStringValue, PrivacyVisibility } from './settings';
|
||||
import type { ApiStarsAmount } from './stars';
|
||||
import type { ApiTypeCurrencyAmount } from './stars';
|
||||
import type { ApiStealthMode, ApiStory, ApiStorySkipped } from './stories';
|
||||
import type {
|
||||
ApiEmojiStatusType, ApiUser, ApiUserFullInfo, ApiUserStatus,
|
||||
@ -794,7 +794,7 @@ export type ApiUpdatePremiumFloodWait = {
|
||||
|
||||
export type ApiUpdateStarsBalance = {
|
||||
'@type': 'updateStarsBalance';
|
||||
balance: ApiStarsAmount;
|
||||
balance: ApiTypeCurrencyAmount;
|
||||
};
|
||||
|
||||
export type ApiUpdateDeleteProfilePhoto = {
|
||||
|
||||
@ -657,6 +657,7 @@
|
||||
"MenuStickers" = "Stickers and Emoji";
|
||||
"MenuAnimations" = "Animations and Performance";
|
||||
"MenuStars" = "My Stars";
|
||||
"MenuTon" = "My TON";
|
||||
"MenuSendGift" = "Send a Gift";
|
||||
"MenuTelegramFaq" = "Telegram FAQ";
|
||||
"MenuPrivacyPolicy" = "Privacy Policy";
|
||||
@ -1880,6 +1881,10 @@
|
||||
"ActionGiftStarsTitle_one" = "{amount} Star";
|
||||
"ActionGiftStarsTitle_other" = "{amount} Stars";
|
||||
"ActionGiftStarsText" = "Use Stars to unlock content and services on Telegram.";
|
||||
"TonAmount" = "💎{amount}";
|
||||
"TonAmountText_one" = "{amount} TON";
|
||||
"TonAmountText_other" = "{amount} TON";
|
||||
"ActionGiftCostCrypto" = "{cryptoPrice} ({price})";
|
||||
"ActionBoostApplyYou_one" = "You boosted the group";
|
||||
"ActionBoostApplyYou_other" = "You boosted the group {count} times";
|
||||
"ActionBoostApply_one" = "{from} boosted the group";
|
||||
@ -2040,11 +2045,14 @@
|
||||
"TitleTime" = "Time";
|
||||
"TitleSuggestMessage" = "Suggest a Message";
|
||||
"TitleSuggestedChanges" = "Suggest Changes";
|
||||
"EnterPriceInStars" = "Enter Price In Stars";
|
||||
"SuggestMessagePriceDescription" = "Choose how many {currency} to pay to publish this post.";
|
||||
"SuggestMessageNoPrice" = "Free";
|
||||
"EnterPriceInStars" = "Enter Price in Stars";
|
||||
"EnterPriceInTon" = "Enter Price in TON";
|
||||
"SuggestMessagePriceDescriptionStars" = "Choose how many Stars to pay to publish this post.";
|
||||
"SuggestMessagePriceDescriptionTon" = "Choose how many TON to pay to publish this post.";
|
||||
"SuggestMessageDateTimeHint" = "Select the date and time you want the post to be published.";
|
||||
"SuggestMessageTimeDescription" = "{hint} The post will remain available for at least {duration} from this date.";
|
||||
"TitleAnytime" = "Anytime";
|
||||
"SuggestMessageAnytime" = "Anytime";
|
||||
"ButtonOfferAmount" = "Offer {amount}";
|
||||
"ButtonOfferFree" = "Offer Free";
|
||||
"ButtonUpdateTerms" = "Update Terms";
|
||||
@ -2070,6 +2078,7 @@
|
||||
"SuggestedPostRefundedByUser" = "{channel} will not receive {amount} because {user} requested a refund.";
|
||||
"SuggestedPostRefundedByChannel" = "{amount} was returned to {peer} because {channel} deleted the message.";
|
||||
"CurrencyStars" = "Stars";
|
||||
"CurrencyTon" = "TON";
|
||||
"DeclineReasonPlaceholder" = "Add a reason (optional)";
|
||||
"DeclinePostDialogQuestion" = "Do you want to decline this post from **{sender}**?";
|
||||
"SuggestedPostRejected" = "**{peer}** rejected this message.";
|
||||
@ -2126,4 +2135,9 @@
|
||||
"ToDoListErrorChooseTitle" = "Please enter a title.";
|
||||
"ToDoListErrorChooseTasks" = "Please enter at least one task.";
|
||||
"GiftInfoCollectibleBy" = "Collectible #{number} by **{owner}**";
|
||||
"PremiumPreviewTodo" = "Checklists";
|
||||
"PremiumPreviewTodo" = "Checklists";
|
||||
"MenuTon" = "My TON";
|
||||
"DescriptionAboutTon" = "Offer TON to submit post suggestions to channels on Telegram.";
|
||||
"ButtonTopUpViaFragment" = "Top-Up Via Fragment";
|
||||
"TonModalHint" = "You can top-up your TON using Fragment.";
|
||||
"TonGiftReceived" = "TON Top-Up";
|
||||
|
||||
BIN
src/assets/tgs/Diamond.tgs
Normal file
BIN
src/assets/tgs/Diamond.tgs
Normal file
Binary file not shown.
@ -156,11 +156,19 @@
|
||||
font-weight: var(--font-weight-semibold) !important;
|
||||
line-height: 1;
|
||||
|
||||
animation: hide-icon 0.4s forwards ease-out;
|
||||
&.visible {
|
||||
animation: grow-icon 0.4s ease-out;
|
||||
}
|
||||
|
||||
&.hiding {
|
||||
animation: hide-icon 0.4s forwards ease-out;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
@ -55,6 +55,7 @@ import {
|
||||
SCHEDULED_WHEN_ONLINE,
|
||||
SEND_MESSAGE_ACTION_INTERVAL,
|
||||
SERVICE_NOTIFICATIONS_USER_ID,
|
||||
STARS_CURRENCY_CODE,
|
||||
} from '../../config';
|
||||
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
||||
import {
|
||||
@ -142,6 +143,7 @@ import useGetSelectionRange from '../../hooks/useGetSelectionRange';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
||||
import useSchedule from '../../hooks/useSchedule';
|
||||
import useSendMessageAction from '../../hooks/useSendMessageAction';
|
||||
@ -1576,7 +1578,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
const handleSuggestPostClick = useLastCallback(() => {
|
||||
updateDraftSuggestedPostInfo({
|
||||
price: { amount: 0, nanos: 0 },
|
||||
price: { currency: STARS_CURRENCY_CODE, amount: 0, nanos: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
@ -1874,6 +1876,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
const effectEmoji = areEffectsSupported && effect?.emoticon;
|
||||
|
||||
const shouldRenderPaidBadge = Boolean(paidMessagesStars && mainButtonState === MainButtonState.Send);
|
||||
const prevShouldRenderPaidBadge = usePrevious(shouldRenderPaidBadge);
|
||||
|
||||
return (
|
||||
<div className={fullClassName}>
|
||||
@ -2358,7 +2361,12 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
{isInMessageList && <Icon name="schedule" />}
|
||||
{isInMessageList && <Icon name="check" />}
|
||||
<Button
|
||||
className={buildClassName('paidStarsBadge', shouldRenderPaidBadge && 'visible')}
|
||||
className={buildClassName(
|
||||
'paidStarsBadge',
|
||||
shouldRenderPaidBadge && 'visible',
|
||||
prevShouldRenderPaidBadge && !shouldRenderPaidBadge && 'hiding',
|
||||
!prevShouldRenderPaidBadge && !shouldRenderPaidBadge && 'hidden',
|
||||
)}
|
||||
nonInteractive
|
||||
size="tiny"
|
||||
color="stars"
|
||||
|
||||
@ -68,6 +68,11 @@
|
||||
text-indent: 0rem;
|
||||
}
|
||||
|
||||
.suggested-price-ton-icon {
|
||||
margin-left: 0rem;
|
||||
text-indent: 0rem;
|
||||
}
|
||||
|
||||
&--background-icons {
|
||||
margin: -0.1875rem -0.375rem -0.1875rem -0.1875rem;
|
||||
}
|
||||
@ -183,6 +188,11 @@
|
||||
content: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.suggested-post-price-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.multiline {
|
||||
|
||||
@ -11,7 +11,7 @@ import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
import type { ChatTranslatedMessages } from '../../../types';
|
||||
import type { IconName } from '../../../types/icons';
|
||||
|
||||
import { CONTENT_NOT_SUPPORTED } from '../../../config';
|
||||
import { CONTENT_NOT_SUPPORTED, TON_CURRENCY_CODE } from '../../../config';
|
||||
import {
|
||||
getMessageIsSpoiler,
|
||||
getMessageMediaHash,
|
||||
@ -26,7 +26,7 @@ import buildClassName from '../../../util/buildClassName';
|
||||
import { formatScheduledDateTime } from '../../../util/dates/dateFormat';
|
||||
import { isUserId } from '../../../util/entities/ids';
|
||||
import freezeWhenClosed from '../../../util/hoc/freezeWhenClosed';
|
||||
import { formatStarsAsIcon } from '../../../util/localization/format';
|
||||
import { formatStarsAsIcon, formatTonAsIcon } from '../../../util/localization/format';
|
||||
import { getPictogramDimensions } from '../helpers/mediaDimensions';
|
||||
import renderText from '../helpers/renderText';
|
||||
import { renderTextWithEntities } from '../helpers/renderTextWithEntities';
|
||||
@ -150,23 +150,34 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
return lang('ComposerEmbeddedMessageSuggestedPostDescription');
|
||||
}
|
||||
const priceText = suggestedPostInfo.price
|
||||
? formatStarsAsIcon(lang, suggestedPostInfo.price.amount, {
|
||||
className: 'suggested-price-star-icon',
|
||||
})
|
||||
? (suggestedPostInfo.price.currency === TON_CURRENCY_CODE
|
||||
? formatTonAsIcon(lang, suggestedPostInfo.price.amount, {
|
||||
className: 'suggested-price-ton-icon',
|
||||
shouldConvertFromNanos: true,
|
||||
})
|
||||
: formatStarsAsIcon(lang, suggestedPostInfo.price.amount, {
|
||||
className: 'suggested-price-star-icon',
|
||||
}))
|
||||
: '';
|
||||
const scheduleText = suggestedPostInfo.scheduleDate
|
||||
? formatScheduledDateTime(suggestedPostInfo.scheduleDate, lang, oldLang)
|
||||
: '';
|
||||
if (priceText && !scheduleText) {
|
||||
return lang('TitleSuggestedPostAmountForAnyTime',
|
||||
{ amount: priceText },
|
||||
{
|
||||
withNodes: true,
|
||||
withMarkdown: true,
|
||||
});
|
||||
return (
|
||||
<span className="suggested-post-price-wrapper">
|
||||
{
|
||||
lang('TitleSuggestedPostAmountForAnyTime',
|
||||
{ amount: priceText },
|
||||
{
|
||||
withNodes: true,
|
||||
withMarkdown: true,
|
||||
})
|
||||
}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span>
|
||||
<span className="suggested-post-price-wrapper">
|
||||
{priceText}
|
||||
{scheduleText ? ` • ${scheduleText}` : ''}
|
||||
</span>
|
||||
|
||||
@ -8,6 +8,7 @@ import VoiceAllowTalk from '../../../assets/tgs/calls/VoiceAllowTalk.tgs';
|
||||
import VoiceMini from '../../../assets/tgs/calls/VoiceMini.tgs';
|
||||
import VoiceMuted from '../../../assets/tgs/calls/VoiceMuted.tgs';
|
||||
import VoiceOutlined from '../../../assets/tgs/calls/VoiceOutlined.tgs';
|
||||
import Diamond from '../../../assets/tgs/Diamond.tgs';
|
||||
import Flame from '../../../assets/tgs/general/Flame.tgs';
|
||||
import Fragment from '../../../assets/tgs/general/Fragment.tgs';
|
||||
import Mention from '../../../assets/tgs/general/Mention.tgs';
|
||||
@ -68,4 +69,5 @@ export const LOCAL_TGS_URLS = {
|
||||
Report,
|
||||
SearchingDuck,
|
||||
BannedDuck,
|
||||
Diamond,
|
||||
};
|
||||
|
||||
@ -2,21 +2,23 @@ import type { FC } from '../../../lib/teact/teact';
|
||||
import { memo, useEffect } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiStarsAmount } from '../../../api/types';
|
||||
import type { ApiStarsAmount, ApiTonAmount } from '../../../api/types';
|
||||
import { SettingsScreens } from '../../../types';
|
||||
|
||||
import { FAQ_URL, PRIVACY_URL } from '../../../config';
|
||||
import { FAQ_URL, PRIVACY_URL, TON_CURRENCY_CODE } from '../../../config';
|
||||
import { formatStarsAmount } from '../../../global/helpers/payments';
|
||||
import {
|
||||
selectIsGiveawayGiftsPurchaseAvailable,
|
||||
selectIsPremiumPurchaseBlocked,
|
||||
} from '../../../global/selectors';
|
||||
import { convertCurrencyFromBaseUnit } from '../../../util/formatCurrency';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import StarIcon from '../../common/icons/StarIcon';
|
||||
import ChatExtra from '../../common/profile/ChatExtra';
|
||||
import ProfileInfo from '../../common/ProfileInfo';
|
||||
@ -34,6 +36,7 @@ type StateProps = {
|
||||
canBuyPremium?: boolean;
|
||||
isGiveawayAvailable?: boolean;
|
||||
starsBalance?: ApiStarsAmount;
|
||||
tonBalance?: ApiTonAmount;
|
||||
};
|
||||
|
||||
const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
@ -43,6 +46,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
canBuyPremium,
|
||||
isGiveawayAvailable,
|
||||
starsBalance,
|
||||
tonBalance,
|
||||
onReset,
|
||||
}) => {
|
||||
const {
|
||||
@ -192,6 +196,18 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
</span>
|
||||
)}
|
||||
</ListItem>
|
||||
<ListItem
|
||||
leftElement={<Icon className="icon ListItem-main-icon" name="toncoin" />}
|
||||
narrow
|
||||
onClick={() => openStarsBalanceModal({ currency: TON_CURRENCY_CODE })}
|
||||
>
|
||||
{lang('MenuTon')}
|
||||
{Boolean(tonBalance) && (
|
||||
<span className="settings-item__current-value">
|
||||
{convertCurrencyFromBaseUnit(tonBalance.amount, tonBalance.currency)}
|
||||
</span>
|
||||
)}
|
||||
</ListItem>
|
||||
{isGiveawayAvailable && (
|
||||
<ListItem
|
||||
icon="gift"
|
||||
@ -245,6 +261,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
const { currentUserId } = global;
|
||||
const isGiveawayAvailable = selectIsGiveawayGiftsPurchaseAvailable(global);
|
||||
const starsBalance = global.stars?.balance;
|
||||
const tonBalance = global.ton?.balance;
|
||||
|
||||
return {
|
||||
sessionCount: global.activeSessions.orderedHashes.length,
|
||||
@ -252,6 +269,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
canBuyPremium: !selectIsPremiumPurchaseBlocked(global),
|
||||
isGiveawayAvailable,
|
||||
starsBalance,
|
||||
tonBalance,
|
||||
};
|
||||
},
|
||||
)(SettingsMain));
|
||||
|
||||
@ -214,6 +214,7 @@ const Main = ({
|
||||
loadAvailableReactions,
|
||||
loadStickerSets,
|
||||
loadPremiumGifts,
|
||||
loadTonGifts,
|
||||
loadStarGifts,
|
||||
loadDefaultTopicIcons,
|
||||
loadAddedStickers,
|
||||
@ -347,6 +348,7 @@ const Main = ({
|
||||
loadUserCollectibleStatuses();
|
||||
loadGenericEmojiEffects();
|
||||
loadPremiumGifts();
|
||||
loadTonGifts();
|
||||
loadStarGifts();
|
||||
loadAvailableEffects();
|
||||
loadBirthdayNumbersStickers();
|
||||
|
||||
@ -23,8 +23,9 @@ import { getPeerTitle } from '../../global/helpers/peers';
|
||||
import { selectChatMessage, selectSender } from '../../global/selectors';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { formatHumanDate, formatScheduledDateTime } from '../../util/dates/dateFormat';
|
||||
import { convertTonFromNanos } from '../../util/formatCurrency';
|
||||
import { compact } from '../../util/iteratees';
|
||||
import { formatStarsAsText } from '../../util/localization/format';
|
||||
import { formatStarsAsText, formatTonAsText } from '../../util/localization/format';
|
||||
import { isAlbum } from './helpers/groupMessages';
|
||||
import { preventMessageInputBlur } from './helpers/preventMessageInputBlur';
|
||||
import { renderPeerLink } from './message/helpers/messageActions';
|
||||
@ -202,8 +203,14 @@ const MessageListContent: FC<OwnProps> = ({
|
||||
: lang('ActionSuggestedPostIncoming', { user: userLink }, { withNodes: true, withMarkdown: true });
|
||||
|
||||
const tableData: TableEntry[] = compact([
|
||||
price && [lang('TitlePrice'), formatStarsAsText(lang, price.amount)],
|
||||
Boolean(scheduleDate) && [lang('TitleTime'), formatScheduledDateTime(scheduleDate, lang, oldLang)],
|
||||
[lang('TitlePrice'), price ? (price.currency === 'TON'
|
||||
? formatTonAsText(lang, convertTonFromNanos(price.amount))
|
||||
: formatStarsAsText(lang, price.amount)) : lang('SuggestMessageNoPrice')],
|
||||
[lang('TitleTime'),
|
||||
scheduleDate
|
||||
? formatScheduledDateTime(scheduleDate, lang, oldLang)
|
||||
: lang('SuggestMessageAnytime'),
|
||||
],
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@ -48,6 +48,7 @@ import {
|
||||
selectCurrentMessageList,
|
||||
selectCurrentMiddleSearch,
|
||||
selectDraft,
|
||||
selectEditingId,
|
||||
selectIsChatBotNotStarted,
|
||||
selectIsCurrentUserFrozen,
|
||||
selectIsInSelectMode,
|
||||
@ -788,6 +789,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
const chatFullInfo = chatId ? selectChatFullInfo(global, chatId) : undefined;
|
||||
const userFullInfo = chatId ? selectUserFullInfo(global, chatId) : undefined;
|
||||
|
||||
const editingId = selectEditingId(global, chatId, threadId);
|
||||
|
||||
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
||||
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
||||
const topic = selectTopic(global, chatId, threadId);
|
||||
@ -814,7 +817,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
? threadId === MAIN_THREAD_ID && !draftReplyInfo && (selectTopic(global, chatId, GENERAL_TOPIC_ID)?.isClosed)
|
||||
: false;
|
||||
const isMonoforumAdmin = selectIsMonoforumAdmin(global, chatId);
|
||||
const shouldBlockSendInMonoforum = Boolean(chat?.isMonoforum && !draftReplyInfo && isMonoforumAdmin);
|
||||
const shouldBlockSendInMonoforum = Boolean(chat?.isMonoforum && !draftReplyInfo && isMonoforumAdmin && !editingId);
|
||||
const topics = selectTopics(global, chatId);
|
||||
|
||||
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
||||
|
||||
@ -294,6 +294,7 @@ const ActionMessage = ({
|
||||
break;
|
||||
}
|
||||
|
||||
case 'giftTon':
|
||||
case 'giftStars': {
|
||||
openStarsTransactionFromGift({
|
||||
chatId: message.chatId,
|
||||
@ -377,6 +378,7 @@ const ActionMessage = ({
|
||||
);
|
||||
|
||||
case 'giftPremium':
|
||||
case 'giftTon':
|
||||
case 'giftStars':
|
||||
return (
|
||||
<Gift
|
||||
|
||||
@ -6,7 +6,9 @@ import type { ApiChat, ApiMessage, ApiPeer } from '../../../api/types';
|
||||
import {
|
||||
GENERAL_TOPIC_ID,
|
||||
SERVICE_NOTIFICATIONS_USER_ID,
|
||||
STARS_CURRENCY_CODE,
|
||||
TME_LINK_PREFIX,
|
||||
TON_CURRENCY_CODE,
|
||||
} from '../../../config';
|
||||
import {
|
||||
getMainUsername,
|
||||
@ -27,7 +29,8 @@ import {
|
||||
import { ensureProtocol } from '../../../util/browser/url';
|
||||
import { formatDateTimeToString, formatScheduledDateTime, formatShortDuration } from '../../../util/dates/dateFormat';
|
||||
import { formatCurrency } from '../../../util/formatCurrency';
|
||||
import { formatStarsAsText } from '../../../util/localization/format';
|
||||
import { convertTonFromNanos } from '../../../util/formatCurrency';
|
||||
import { formatStarsAsText, formatTonAsText } from '../../../util/localization/format';
|
||||
import { conjuctionWithNodes } from '../../../util/localization/utils';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
@ -441,15 +444,17 @@ const ActionMessageText = ({
|
||||
}
|
||||
|
||||
case 'giftStars':
|
||||
case 'giftPremium': {
|
||||
case 'giftPremium':
|
||||
case 'giftTon': {
|
||||
const {
|
||||
amount, currency, cryptoAmount, cryptoCurrency,
|
||||
amount, currency, cryptoAmount, cryptoCurrency, type,
|
||||
} = action;
|
||||
|
||||
const price = formatCurrency(lang, amount, currency, { asFontIcon: true });
|
||||
const cryptoPrice = cryptoAmount ? formatCurrency(lang, cryptoAmount, cryptoCurrency!) : undefined;
|
||||
const cryptoPrice = cryptoAmount && type !== 'giftTon'
|
||||
? formatCurrency(lang, cryptoAmount, cryptoCurrency!) : undefined;
|
||||
|
||||
const cost = cryptoPrice ? lang('ActionCostCrypto', { price, cryptoPrice }, { withNodes: true }) : price;
|
||||
const cost = cryptoPrice ? lang('ActionGiftCostCrypto', { price, cryptoPrice }, { withNodes: true }) : price;
|
||||
|
||||
if (isServiceNotificationsChat) {
|
||||
return lang('ActionGiftTextCostAnonymous', { cost }, { withNodes: true });
|
||||
@ -755,13 +760,21 @@ const ActionMessageText = ({
|
||||
}
|
||||
|
||||
case 'suggestedPostSuccess': {
|
||||
const { amount: stars } = action;
|
||||
const { amount: price } = action;
|
||||
const currency = price?.currency || STARS_CURRENCY_CODE;
|
||||
const amount = price?.amount || 0;
|
||||
|
||||
const channel = chat?.isMonoforum ? selectMonoforumChannel(global, chatId) : chat;
|
||||
const channelTitle = channel && getPeerTitle(lang, channel);
|
||||
const channelLink = renderPeerLink(channel?.id, channelTitle || channelFallbackText, asPreview);
|
||||
|
||||
const formattedAmount = currency === TON_CURRENCY_CODE
|
||||
? formatTonAsText(lang, convertTonFromNanos(amount))
|
||||
: formatStarsAsText(lang, amount);
|
||||
|
||||
return lang('ActionSuggestedPostSuccess', {
|
||||
channel: channelLink,
|
||||
amount: formatStarsAsText(lang, stars?.amount || 0),
|
||||
amount: formattedAmount,
|
||||
}, { withNodes: true });
|
||||
}
|
||||
case 'suggestedPostRefund': {
|
||||
@ -775,22 +788,28 @@ const ActionMessageText = ({
|
||||
const postSenderTitle = postSender && getPeerTitle(lang, postSender);
|
||||
const postSenderLink = renderPeerLink(postSender?.id, postSenderTitle || userFallbackText, asPreview);
|
||||
|
||||
const starsAmount = replyMessage?.suggestedPostInfo?.price?.amount || 0;
|
||||
const price = replyMessage?.suggestedPostInfo?.price;
|
||||
const currency = price?.currency || STARS_CURRENCY_CODE;
|
||||
const amount = price?.amount || 0;
|
||||
|
||||
const channel = chat?.isMonoforum ? selectMonoforumChannel(global, chatId) : chat;
|
||||
const channelTitle = channel && getPeerTitle(lang, channel);
|
||||
const channelLink = renderPeerLink(channel?.id, channelTitle || channelFallbackText, asPreview);
|
||||
|
||||
const formattedAmount = currency === TON_CURRENCY_CODE
|
||||
? formatTonAsText(lang, convertTonFromNanos(amount))
|
||||
: formatStarsAsText(lang, amount);
|
||||
|
||||
if (payerInitiated) {
|
||||
return lang('SuggestedPostRefundedByUser', {
|
||||
amount: formatStarsAsText(lang, starsAmount),
|
||||
amount: formattedAmount,
|
||||
user: postSenderLink,
|
||||
channel: channelLink,
|
||||
}, { withNodes: true, withMarkdown: true });
|
||||
}
|
||||
|
||||
return lang('SuggestedPostRefundedByChannel', {
|
||||
amount: formatStarsAsText(lang, starsAmount),
|
||||
amount: formattedAmount,
|
||||
peer: postSenderLink,
|
||||
channel: channelLink,
|
||||
}, { withNodes: true, withMarkdown: true });
|
||||
@ -819,9 +838,13 @@ const ActionMessageText = ({
|
||||
const replyMessageSender = replyMessage ? selectSender(global, replyMessage) : sender;
|
||||
const replyPeerTitle = replyMessageSender && getPeerTitle(lang, replyMessageSender);
|
||||
const userLink = renderPeerLink(replyMessageSender?.id, replyPeerTitle || userFallbackText, asPreview);
|
||||
|
||||
const currency = replyMessage?.suggestedPostInfo?.price?.currency || STARS_CURRENCY_CODE;
|
||||
const currencyName = currency === TON_CURRENCY_CODE ? lang('CurrencyTon') : lang('CurrencyStars');
|
||||
|
||||
return lang('SuggestedPostBalanceTooLow', {
|
||||
peer: userLink,
|
||||
currency: lang('CurrencyStars'),
|
||||
currency: currencyName,
|
||||
}, { withNodes: true, withMarkdown: true });
|
||||
}
|
||||
|
||||
|
||||
@ -2,13 +2,18 @@ import { memo, useRef } from '../../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../../global';
|
||||
|
||||
import type { ApiSticker } from '../../../../api/types';
|
||||
import type { ApiMessageActionGiftPremium, ApiMessageActionGiftStars } from '../../../../api/types/messageActions';
|
||||
import type {
|
||||
ApiMessageActionGiftPremium,
|
||||
ApiMessageActionGiftStars,
|
||||
ApiMessageActionGiftTon } from '../../../../api/types/messageActions';
|
||||
|
||||
import {
|
||||
selectCanPlayAnimatedEmojis,
|
||||
selectGiftStickerForDuration,
|
||||
selectGiftStickerForStars,
|
||||
selectGiftStickerForTon,
|
||||
} from '../../../../global/selectors';
|
||||
import { formatCurrency } from '../../../../util/formatCurrency';
|
||||
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
||||
|
||||
import { type ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
||||
@ -20,7 +25,7 @@ import StickerView from '../../../common/StickerView';
|
||||
import styles from '../ActionMessage.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
action: ApiMessageActionGiftPremium | ApiMessageActionGiftStars;
|
||||
action: ApiMessageActionGiftPremium | ApiMessageActionGiftStars | ApiMessageActionGiftTon;
|
||||
observeIntersectionForLoading?: ObserveFn;
|
||||
observeIntersectionForPlaying?: ObserveFn;
|
||||
onClick?: NoneToVoidFunction;
|
||||
@ -45,6 +50,15 @@ const GiftAction = ({
|
||||
const lang = useLang();
|
||||
const message = action.type === 'giftPremium' ? action.message : undefined;
|
||||
|
||||
const renderTonTitle = () => {
|
||||
const { cryptoAmount, cryptoCurrency } = action;
|
||||
const price = cryptoAmount
|
||||
? formatCurrency(lang, cryptoAmount, cryptoCurrency!, { asFontIcon: true })
|
||||
: undefined;
|
||||
|
||||
return price;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.contentBox} tabIndex={0} role="button" onClick={onClick}>
|
||||
<div
|
||||
@ -67,13 +81,16 @@ const GiftAction = ({
|
||||
<h3 className={styles.title}>
|
||||
{action.type === 'giftPremium' ? (
|
||||
lang('ActionGiftPremiumTitle', { months: action.months }, { pluralValue: action.months })
|
||||
) : (
|
||||
) : action.type === 'giftStars' ? (
|
||||
lang('ActionGiftStarsTitle', { amount: action.stars }, { pluralValue: action.stars })
|
||||
)}
|
||||
) : renderTonTitle()}
|
||||
</h3>
|
||||
<div>
|
||||
{message && renderTextWithEntities(message)}
|
||||
{!message && (lang(action.type === 'giftPremium' ? 'ActionGiftPremiumText' : 'ActionGiftStarsText'))}
|
||||
{!message
|
||||
&& (lang(action.type === 'giftTon' ? 'DescriptionAboutTon'
|
||||
: action.type === 'giftPremium'
|
||||
? 'ActionGiftPremiumText' : 'ActionGiftStarsText'))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.actionButton}>
|
||||
@ -88,7 +105,9 @@ export default memo(withGlobal<OwnProps>(
|
||||
(global, { action }): StateProps => {
|
||||
const sticker = action.type === 'giftPremium'
|
||||
? selectGiftStickerForDuration(global, action.months)
|
||||
: selectGiftStickerForStars(global, action.stars);
|
||||
: action.type === 'giftStars'
|
||||
? selectGiftStickerForStars(global, action.stars)
|
||||
: selectGiftStickerForTon(global, action.cryptoAmount);
|
||||
const canPlayAnimatedEmojis = selectCanPlayAnimatedEmojis(global);
|
||||
|
||||
return {
|
||||
|
||||
@ -4,7 +4,7 @@ import { withGlobal } from '../../../../global';
|
||||
import type { ApiMessage, ApiPeer } from '../../../../api/types';
|
||||
import type { ApiMessageActionSuggestedPostApproval } from '../../../../api/types/messageActions';
|
||||
|
||||
import { STARS_SUGGESTED_POST_AGE_MIN } from '../../../../config';
|
||||
import { STARS_SUGGESTED_POST_AGE_MIN, TON_CURRENCY_CODE } from '../../../../config';
|
||||
import { getPeerFullTitle } from '../../../../global/helpers/peers';
|
||||
import { getMessageReplyInfo } from '../../../../global/helpers/replies';
|
||||
import { selectIsMonoforumAdmin, selectMonoforumChannel,
|
||||
@ -12,7 +12,8 @@ import { selectIsMonoforumAdmin, selectMonoforumChannel,
|
||||
selectSender } from '../../../../global/selectors';
|
||||
import buildClassName from '../../../../util/buildClassName';
|
||||
import { formatScheduledDateTime, formatShortDuration } from '../../../../util/dates/dateFormat';
|
||||
import { formatStarsAsText } from '../../../../util/localization/format';
|
||||
import { convertTonFromNanos } from '../../../../util/formatCurrency';
|
||||
import { formatStarsAsText, formatTonAsText } from '../../../../util/localization/format';
|
||||
import { getServerTime } from '../../../../util/serverTime';
|
||||
import renderText from '../../../common/helpers/renderText';
|
||||
import { renderPeerLink, translateWithYou } from '../helpers/messageActions';
|
||||
@ -58,11 +59,18 @@ const SuggestedPostApproval = ({
|
||||
|
||||
const publishDate = scheduleDate
|
||||
? formatScheduledDateTime(scheduleDate, lang, oldLang)
|
||||
: lang('TitleAnytime');
|
||||
: lang('SuggestMessageAnytime');
|
||||
|
||||
const isPostPublished = scheduleDate ? scheduleDate <= getServerTime() : false;
|
||||
|
||||
const starsText = amount?.amount ? formatStarsAsText(lang, amount.amount) : undefined;
|
||||
const currency = amount?.currency;
|
||||
const amountValue = amount?.amount || 0;
|
||||
|
||||
const formattedAmount = amountValue > 0
|
||||
? (currency === TON_CURRENCY_CODE
|
||||
? formatTonAsText(lang, convertTonFromNanos(amountValue))
|
||||
: formatStarsAsText(lang, amountValue))
|
||||
: undefined;
|
||||
|
||||
const duration = formatShortDuration(lang, ageMinSeconds, true);
|
||||
|
||||
@ -85,31 +93,35 @@ const SuggestedPostApproval = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{starsText && (
|
||||
{formattedAmount && (
|
||||
<div className={styles.suggestedPostApprovalSection}>
|
||||
{translateWithYou(lang,
|
||||
'SuggestedPostCharged',
|
||||
!isAdmin,
|
||||
{
|
||||
user: originalSenderLink,
|
||||
amount: starsText,
|
||||
amount: formattedAmount,
|
||||
},
|
||||
{ withMarkdown: true },
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isPostPublished && starsText && (
|
||||
{isPostPublished && formattedAmount && (
|
||||
<>
|
||||
<div className={styles.suggestedPostApprovalSection}>
|
||||
{translateWithYou(lang, 'SuggestedPostReceiveAmount', !isAdmin, {
|
||||
peer: renderChatLink(), duration, currency: lang('CurrencyStars'),
|
||||
peer: renderChatLink(),
|
||||
duration,
|
||||
currency: currency === TON_CURRENCY_CODE ? lang('CurrencyTon') : lang('CurrencyStars'),
|
||||
}, { withMarkdown: true })}
|
||||
</div>
|
||||
|
||||
<div className={styles.suggestedPostApprovalSection}>
|
||||
{translateWithYou(lang, 'SuggestedPostRefund', !isAdmin, {
|
||||
peer: renderChatLink(), duration, currency: lang('CurrencyStars'),
|
||||
peer: renderChatLink(),
|
||||
duration,
|
||||
currency: currency === TON_CURRENCY_CODE ? lang('CurrencyTon') : lang('CurrencyStars'),
|
||||
}, { withMarkdown: true })}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -4,6 +4,7 @@ import { getActions, withGlobal } from '../../../../global';
|
||||
import type { ApiMessage, ApiPeer } from '../../../../api/types';
|
||||
import type { ApiMessageActionSuggestedPostApproval } from '../../../../api/types/messageActions';
|
||||
|
||||
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../../config';
|
||||
import { getPeerFullTitle } from '../../../../global/helpers/peers';
|
||||
import { selectChatMessage, selectSender } from '../../../../global/selectors';
|
||||
import buildClassName from '../../../../util/buildClassName';
|
||||
@ -25,6 +26,7 @@ type OwnProps = {
|
||||
type StateProps = {
|
||||
sender?: ApiPeer;
|
||||
replyMessageSender?: ApiPeer;
|
||||
replyMessage?: ApiMessage;
|
||||
};
|
||||
|
||||
const SuggestedPostBalanceTooLow = ({
|
||||
@ -32,6 +34,7 @@ const SuggestedPostBalanceTooLow = ({
|
||||
message,
|
||||
sender,
|
||||
replyMessageSender,
|
||||
replyMessage,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { openStarsBalanceModal } = getActions();
|
||||
const lang = useLang();
|
||||
@ -46,6 +49,10 @@ const SuggestedPostBalanceTooLow = ({
|
||||
const peerTitle = targetPeer && getPeerFullTitle(lang, targetPeer);
|
||||
const peerLink = renderPeerLink(targetPeer?.id, peerTitle || lang('ActionFallbackUser'));
|
||||
|
||||
const currency = replyMessage?.suggestedPostInfo?.price?.currency || STARS_CURRENCY_CODE;
|
||||
const currencyName = currency === TON_CURRENCY_CODE ? lang('CurrencyTon') : lang('CurrencyStars');
|
||||
const buyButtonText = currency === TON_CURRENCY_CODE ? lang('ButtonTopUpViaFragment') : lang('ButtonBuyStars');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={buildClassName(styles.contentBox, styles.suggestedPostBalanceTooLowBox)}
|
||||
@ -54,14 +61,14 @@ const SuggestedPostBalanceTooLow = ({
|
||||
<div className={styles.suggestedPostBalanceTooLowTitle}>
|
||||
{lang('SuggestedPostBalanceTooLow', {
|
||||
peer: peerLink,
|
||||
currency: lang('CurrencyStars'),
|
||||
currency: currencyName,
|
||||
}, { withNodes: true, withMarkdown: true })}
|
||||
</div>
|
||||
|
||||
{!message.isOutgoing && (
|
||||
<div className={styles.actionButton} onClick={handleGetMoreStars}>
|
||||
<Sparkles preset="button" />
|
||||
{lang('ButtonBuyStars')}
|
||||
{buyButtonText}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -81,6 +88,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
return {
|
||||
sender,
|
||||
replyMessageSender,
|
||||
replyMessage,
|
||||
};
|
||||
},
|
||||
)(SuggestedPostBalanceTooLow));
|
||||
|
||||
@ -124,7 +124,7 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
||||
const allGifts = renderingModal?.gifts;
|
||||
const filteredGifts = useMemo(() => {
|
||||
return allGifts?.sort((prevGift, gift) => prevGift.months - gift.months)
|
||||
.filter((gift) => gift.users === 1 && gift.currency !== 'XTR');
|
||||
.filter((gift) => gift.users === 1 && gift.currency !== STARS_CURRENCY_CODE);
|
||||
}, [allGifts]);
|
||||
|
||||
const giftsByStars = useMemo(() => {
|
||||
|
||||
@ -5,7 +5,7 @@ import { getActions } from '../../../global';
|
||||
import type {
|
||||
ApiPeer,
|
||||
ApiStarGiftAttributeBackdrop, ApiStarGiftAttributeModel, ApiStarGiftAttributePattern,
|
||||
ApiStarsAmount } from '../../../api/types';
|
||||
ApiTypeCurrencyAmount } from '../../../api/types';
|
||||
|
||||
import {
|
||||
formatStarsTransactionAmount,
|
||||
@ -31,7 +31,7 @@ type OwnProps = {
|
||||
subtitle?: TeactNode;
|
||||
subtitlePeer?: ApiPeer;
|
||||
className?: string;
|
||||
resellPrice?: ApiStarsAmount;
|
||||
resellPrice?: ApiTypeCurrencyAmount;
|
||||
};
|
||||
|
||||
const STICKER_SIZE = 120;
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { memo } from '../../../lib/teact/teact';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type { ApiStarsAmount } from '../../../api/types';
|
||||
import type { ApiTypeCurrencyAmount } from '../../../api/types';
|
||||
|
||||
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../config';
|
||||
import { formatStarsAmount } from '../../../global/helpers/payments';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { convertCurrencyFromBaseUnit } from '../../../util/formatCurrency';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
@ -15,7 +17,7 @@ import StarIcon from '../../common/icons/StarIcon';
|
||||
import styles from './StarsBalanceModal.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
balance?: ApiStarsAmount;
|
||||
balance?: ApiTypeCurrencyAmount;
|
||||
withAddButton?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
@ -27,25 +29,42 @@ const BalanceBlock = ({ balance, className, withAddButton }: OwnProps) => {
|
||||
openStarsBalanceModal,
|
||||
} = getActions();
|
||||
|
||||
const renderStarsAmount = () => {
|
||||
return (
|
||||
<>
|
||||
<StarIcon type="gold" size="middle" />
|
||||
{balance !== undefined && balance.currency === STARS_CURRENCY_CODE
|
||||
? formatStarsAmount(lang, balance) : '…'}
|
||||
{withAddButton && (
|
||||
<BadgeButton
|
||||
className={styles.addStarsButton}
|
||||
onClick={() => openStarsBalanceModal({})}
|
||||
>
|
||||
<Icon
|
||||
className={styles.addStarsIcon}
|
||||
name="add"
|
||||
/>
|
||||
</BadgeButton>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTonAmount = () => {
|
||||
return (
|
||||
<>
|
||||
<Icon name="toncoin" />
|
||||
{balance !== undefined ? convertCurrencyFromBaseUnit(balance.amount, balance.currency) : '…'}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={buildClassName(styles.balanceBlock, className)}>
|
||||
<div className={styles.balanceInfo}>
|
||||
<span className={styles.smallerText}>{lang('StarsBalance')}</span>
|
||||
<div className={styles.balanceBottom}>
|
||||
<StarIcon type="gold" size="middle" />
|
||||
{balance !== undefined ? formatStarsAmount(lang, balance) : '…'}
|
||||
{withAddButton && (
|
||||
<BadgeButton
|
||||
className={styles.addStarsButton}
|
||||
|
||||
onClick={() => openStarsBalanceModal({})}
|
||||
>
|
||||
<Icon
|
||||
className={styles.addStarsIcon}
|
||||
name="add"
|
||||
/>
|
||||
</BadgeButton>
|
||||
)}
|
||||
{balance?.currency === TON_CURRENCY_CODE ? renderTonAmount() : renderStarsAmount()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -56,6 +56,7 @@
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.hint,
|
||||
.tos {
|
||||
padding: 0.5rem 1rem;
|
||||
padding-top: 0;
|
||||
@ -73,6 +74,37 @@
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.topUpButton,
|
||||
.tonBalanceContainer {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tonBalance {
|
||||
unicode-bidi: plaintext;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
font-size: 1.75rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.tonIconBalance {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.tonInUsd {
|
||||
font-size: 1rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.tonIconLogo {
|
||||
padding-top: 1.5rem;
|
||||
padding-bottom: 1rem;
|
||||
font-size: 5rem;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.logoBackground {
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
@ -196,6 +228,7 @@
|
||||
right: 1.25rem;
|
||||
}
|
||||
|
||||
.topUpButton,
|
||||
.starButton {
|
||||
grid-column: 1/-1;
|
||||
gap: 0.5rem;
|
||||
|
||||
@ -8,11 +8,19 @@ import type { ApiStarTopupOption } from '../../../api/types';
|
||||
import type { GlobalState, TabState } from '../../../global/types';
|
||||
import type { RegularLangKey } from '../../../types/language';
|
||||
|
||||
import { PAID_MESSAGES_PURPOSE } from '../../../config';
|
||||
import {
|
||||
PAID_MESSAGES_PURPOSE,
|
||||
STARS_CURRENCY_CODE,
|
||||
TON_CURRENCY_CODE,
|
||||
TON_TOPUP_URL_DEFAULT,
|
||||
TON_USD_RATE_DEFAULT,
|
||||
} from '../../../config';
|
||||
import { getChatTitle, getUserFullName } from '../../../global/helpers';
|
||||
import { getPeerTitle } from '../../../global/helpers/peers';
|
||||
import { selectChat, selectIsPremiumPurchaseBlocked, selectUser } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { convertCurrencyFromBaseUnit, convertTonToUsd, formatCurrencyAsString } from '../../../util/formatCurrency';
|
||||
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
@ -20,6 +28,7 @@ import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import SafeLink from '../../common/SafeLink';
|
||||
import Button from '../../ui/Button';
|
||||
@ -52,18 +61,25 @@ export type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
starsBalanceState?: GlobalState['stars'];
|
||||
tonBalanceState?: GlobalState['ton'];
|
||||
canBuyPremium?: boolean;
|
||||
shouldForceHeight?: boolean;
|
||||
tonUsdRate?: number;
|
||||
tonTopupUrl: string;
|
||||
};
|
||||
|
||||
const StarsBalanceModal = ({
|
||||
modal, starsBalanceState, canBuyPremium, shouldForceHeight,
|
||||
modal, starsBalanceState, tonBalanceState, canBuyPremium, shouldForceHeight, tonUsdRate, tonTopupUrl,
|
||||
}: OwnProps & StateProps) => {
|
||||
const {
|
||||
closeStarsBalanceModal, loadStarsTransactions, loadStarsSubscriptions, openStarsGiftingPickerModal, openInvoice,
|
||||
openUrl,
|
||||
} = getActions();
|
||||
|
||||
const { balance, history, subscriptions } = starsBalanceState || {};
|
||||
const currency = modal?.currency || STARS_CURRENCY_CODE;
|
||||
const currentState = currency === TON_CURRENCY_CODE ? tonBalanceState : starsBalanceState;
|
||||
const { balance, history } = currentState || {};
|
||||
const { subscriptions } = starsBalanceState || {};
|
||||
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
@ -72,7 +88,7 @@ const StarsBalanceModal = ({
|
||||
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
|
||||
const [areBuyOptionsShown, showBuyOptions, hideBuyOptions] = useFlag();
|
||||
|
||||
const isOpen = Boolean(modal && starsBalanceState);
|
||||
const isOpen = Boolean(modal && (starsBalanceState || tonBalanceState));
|
||||
|
||||
const {
|
||||
originStarsPayment, originReaction, originGift, topup,
|
||||
@ -159,6 +175,90 @@ const StarsBalanceModal = ({
|
||||
];
|
||||
}, [isOpen, oldLang]);
|
||||
|
||||
const renderStarsSection = () => {
|
||||
return (
|
||||
<>
|
||||
<img className={styles.logo} src={StarLogo} alt="" draggable={false} />
|
||||
<img className={styles.logoBackground} src={StarsBackground} alt="" draggable={false} />
|
||||
<h2 className={styles.headerText}>
|
||||
{starsNeeded ? oldLang('StarsNeededTitle', ongoingTransactionAmount) : oldLang('TelegramStars')}
|
||||
</h2>
|
||||
<div className={styles.description}>
|
||||
{renderText(
|
||||
starsNeededText || oldLang('TelegramStarsInfo'),
|
||||
['simple_markdown', 'emoji'],
|
||||
)}
|
||||
</div>
|
||||
{canBuyPremium && !areBuyOptionsShown && (
|
||||
<Button
|
||||
className={styles.starButton}
|
||||
onClick={showBuyOptions}
|
||||
>
|
||||
{oldLang('Star.List.BuyMoreStars')}
|
||||
</Button>
|
||||
)}
|
||||
{canBuyPremium && !areBuyOptionsShown && shouldSuggestGifting && (
|
||||
<Button
|
||||
isText
|
||||
noForcedUpperCase
|
||||
className={styles.starButton}
|
||||
onClick={openStarsGiftingPickerModalHandler}
|
||||
>
|
||||
{oldLang('TelegramStarsGift')}
|
||||
</Button>
|
||||
)}
|
||||
{areBuyOptionsShown && starsBalanceState?.topupOptions && (
|
||||
<StarTopupOptionList
|
||||
starsNeeded={starsNeeded}
|
||||
options={starsBalanceState.topupOptions}
|
||||
onClick={handleBuyStars}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTonSection = () => {
|
||||
const tonAmount = convertCurrencyFromBaseUnit(balance?.amount || 0, TON_CURRENCY_CODE);
|
||||
return (
|
||||
<>
|
||||
<AnimatedIconWithPreview
|
||||
size={160}
|
||||
tgsUrl={LOCAL_TGS_URLS.Diamond}
|
||||
nonInteractive
|
||||
noLoop={false}
|
||||
/>
|
||||
<h2 className={styles.headerText}>
|
||||
{lang('CurrencyTon')}
|
||||
</h2>
|
||||
<div className={styles.description}>
|
||||
{lang('DescriptionAboutTon')}
|
||||
</div>
|
||||
<div className={styles.tonBalanceContainer}>
|
||||
<div className={styles.tonBalance}>
|
||||
<Icon name="toncoin" className={styles.tonIconBalance} />
|
||||
{tonAmount}
|
||||
</div>
|
||||
{Boolean(tonUsdRate) && (
|
||||
<span className={styles.tonInUsd}>
|
||||
{`≈ ${formatCurrencyAsString(
|
||||
convertTonToUsd(balance?.amount || 0, tonUsdRate),
|
||||
'USD',
|
||||
lang.code,
|
||||
)}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
className={styles.topUpButton}
|
||||
onClick={handleTonTopUp}
|
||||
>
|
||||
{lang('ButtonTopUpViaFragment')}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function handleScroll(e: React.UIEvent<HTMLDivElement>) {
|
||||
const { scrollTop } = e.currentTarget;
|
||||
|
||||
@ -168,6 +268,7 @@ const StarsBalanceModal = ({
|
||||
const handleLoadMoreTransactions = useLastCallback(() => {
|
||||
loadStarsTransactions({
|
||||
type: TRANSACTION_TYPES[selectedTabIndex],
|
||||
isTon: currency === TON_CURRENCY_CODE,
|
||||
});
|
||||
});
|
||||
|
||||
@ -188,6 +289,10 @@ const StarsBalanceModal = ({
|
||||
});
|
||||
});
|
||||
|
||||
const handleTonTopUp = useLastCallback(() => {
|
||||
openUrl({ url: tonTopupUrl });
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className={buildClassName(styles.root, !shouldForceHeight && !areBuyOptionsShown && styles.minimal)}
|
||||
@ -206,55 +311,25 @@ const StarsBalanceModal = ({
|
||||
>
|
||||
<Icon name="close" />
|
||||
</Button>
|
||||
<BalanceBlock balance={balance} className={styles.modalBalance} />
|
||||
{currency !== TON_CURRENCY_CODE && <BalanceBlock balance={balance} className={styles.modalBalance} />}
|
||||
<div className={buildClassName(styles.header, isHeaderHidden && styles.hiddenHeader)}>
|
||||
<h2 className={styles.starHeaderText}>
|
||||
{oldLang('TelegramStars')}
|
||||
</h2>
|
||||
</div>
|
||||
<div className={styles.section}>
|
||||
<img className={styles.logo} src={StarLogo} alt="" draggable={false} />
|
||||
<img className={styles.logoBackground} src={StarsBackground} alt="" draggable={false} />
|
||||
<h2 className={styles.headerText}>
|
||||
{starsNeeded ? oldLang('StarsNeededTitle', ongoingTransactionAmount) : oldLang('TelegramStars')}
|
||||
</h2>
|
||||
<div className={styles.description}>
|
||||
{renderText(
|
||||
starsNeededText || oldLang('TelegramStarsInfo'),
|
||||
['simple_markdown', 'emoji'],
|
||||
)}
|
||||
</div>
|
||||
{canBuyPremium && !areBuyOptionsShown && (
|
||||
<Button
|
||||
className={styles.starButton}
|
||||
onClick={showBuyOptions}
|
||||
>
|
||||
{oldLang('Star.List.BuyMoreStars')}
|
||||
</Button>
|
||||
)}
|
||||
{canBuyPremium && !areBuyOptionsShown && shouldSuggestGifting && (
|
||||
<Button
|
||||
isText
|
||||
noForcedUpperCase
|
||||
className={styles.starButton}
|
||||
onClick={openStarsGiftingPickerModalHandler}
|
||||
>
|
||||
{oldLang('TelegramStarsGift')}
|
||||
</Button>
|
||||
)}
|
||||
{areBuyOptionsShown && starsBalanceState?.topupOptions && (
|
||||
<StarTopupOptionList
|
||||
starsNeeded={starsNeeded}
|
||||
options={starsBalanceState.topupOptions}
|
||||
onClick={handleBuyStars}
|
||||
/>
|
||||
)}
|
||||
{currency === TON_CURRENCY_CODE ? renderTonSection() : renderStarsSection()}
|
||||
</div>
|
||||
{areBuyOptionsShown && (
|
||||
<div className={styles.tos}>
|
||||
{tosText}
|
||||
</div>
|
||||
)}
|
||||
{currency === TON_CURRENCY_CODE && (
|
||||
<div className={styles.hint}>
|
||||
{lang('TonModalHint')}
|
||||
</div>
|
||||
)}
|
||||
{shouldShowItems && Boolean(subscriptions?.list.length) && (
|
||||
<div className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>{oldLang('StarMySubscriptions')}</h3>
|
||||
@ -325,13 +400,18 @@ const StarsBalanceModal = ({
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const shouldForceHeight = Boolean(global.stars?.history?.all?.transactions.length);
|
||||
(global, { modal }): StateProps => {
|
||||
const shouldForceHeight = modal?.currency === TON_CURRENCY_CODE
|
||||
? Boolean(global.ton?.history?.all?.transactions.length)
|
||||
: Boolean(global.stars?.history?.all?.transactions.length);
|
||||
|
||||
return {
|
||||
shouldForceHeight,
|
||||
starsBalanceState: global.stars,
|
||||
tonBalanceState: global.ton,
|
||||
canBuyPremium: !selectIsPremiumPurchaseBlocked(global),
|
||||
tonUsdRate: global.appConfig?.tonUsdRate || TON_USD_RATE_DEFAULT,
|
||||
tonTopupUrl: global.appConfig?.tonTopupUrl || TON_TOPUP_URL_DEFAULT,
|
||||
};
|
||||
},
|
||||
)(StarsBalanceModal));
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { ApiStarsAmount, ApiStarsTransaction } from '../../../../api/types';
|
||||
import type { ApiStarsAmount, ApiStarsTransaction, ApiTypeCurrencyAmount } from '../../../../api/types';
|
||||
import type { OldLangFn } from '../../../../hooks/useOldLang';
|
||||
|
||||
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../../config';
|
||||
import { buildStarsTransactionCustomPeer } from '../../../../global/helpers/payments';
|
||||
import {
|
||||
type LangFn,
|
||||
@ -20,7 +21,7 @@ export function getTransactionTitle(oldLang: OldLangFn, lang: LangFn, transactio
|
||||
}
|
||||
|
||||
if (transaction.isGiftResale) {
|
||||
return isNegativeStarsAmount(transaction.stars)
|
||||
return isNegativeAmount(transaction.amount)
|
||||
? lang('StarGiftSaleTransaction')
|
||||
: lang('StarGiftPurchaseTransaction');
|
||||
}
|
||||
@ -34,9 +35,14 @@ export function getTransactionTitle(oldLang: OldLangFn, lang: LangFn, transactio
|
||||
if (transaction.isReaction) return oldLang('StarsReactionsSent');
|
||||
if (transaction.giveawayPostId) return oldLang('StarsGiveawayPrizeReceived');
|
||||
if (transaction.isMyGift) return oldLang('StarsGiftSent');
|
||||
if (transaction.isGift) return oldLang('StarsGiftReceived');
|
||||
if (transaction.isGift) {
|
||||
if (transaction.amount.currency === TON_CURRENCY_CODE) {
|
||||
return lang('TonGiftReceived');
|
||||
}
|
||||
return oldLang('StarsGiftReceived');
|
||||
}
|
||||
if (transaction.starGift) {
|
||||
return isNegativeStarsAmount(transaction.stars) ? oldLang('Gift2TransactionSent') : oldLang('Gift2ConvertedTitle');
|
||||
return isNegativeAmount(transaction.amount) ? oldLang('Gift2TransactionSent') : oldLang('Gift2ConvertedTitle');
|
||||
}
|
||||
|
||||
const customPeer = (transaction.peer && transaction.peer.type !== 'peer'
|
||||
@ -51,3 +57,10 @@ export function isNegativeStarsAmount(starsAmount: ApiStarsAmount) {
|
||||
if (starsAmount.amount) return starsAmount.amount < 0;
|
||||
return starsAmount.nanos < 0;
|
||||
}
|
||||
|
||||
export function isNegativeAmount(currencyAmount: ApiTypeCurrencyAmount) {
|
||||
if (currencyAmount.currency === STARS_CURRENCY_CODE) {
|
||||
return isNegativeStarsAmount(currencyAmount);
|
||||
}
|
||||
return currencyAmount.amount < 0;
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import type {
|
||||
import type { GlobalState } from '../../../../global/types';
|
||||
import type { CustomPeer } from '../../../../types';
|
||||
|
||||
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../../config';
|
||||
import { buildStarsTransactionCustomPeer, formatStarsTransactionAmount } from '../../../../global/helpers/payments';
|
||||
import { getPeerTitle } from '../../../../global/helpers/peers';
|
||||
import { selectPeer } from '../../../../global/selectors';
|
||||
@ -16,7 +17,7 @@ import { formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
||||
import { CUSTOM_PEER_PREMIUM } from '../../../../util/objects/customPeer';
|
||||
import { getGiftAttributes, getStickerFromGift } from '../../../common/helpers/gifts';
|
||||
import renderText from '../../../common/helpers/renderText';
|
||||
import { getTransactionTitle, isNegativeStarsAmount } from '../helpers/transaction';
|
||||
import { getTransactionTitle, isNegativeAmount } from '../helpers/transaction';
|
||||
|
||||
import useSelector from '../../../../hooks/data/useSelector';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
@ -48,7 +49,7 @@ const StarsTransactionItem = ({ transaction, className }: OwnProps) => {
|
||||
const { openStarsTransactionModal } = getActions();
|
||||
const {
|
||||
date,
|
||||
stars,
|
||||
amount,
|
||||
photo,
|
||||
peer: transactionPeer,
|
||||
extendedMedia,
|
||||
@ -73,7 +74,10 @@ const StarsTransactionItem = ({ transaction, className }: OwnProps) => {
|
||||
description = peer && getPeerTitle(oldLang, peer);
|
||||
avatarPeer = peer || CUSTOM_PEER_PREMIUM;
|
||||
} else {
|
||||
const customPeer = buildStarsTransactionCustomPeer(transaction.peer);
|
||||
const customPeer = buildStarsTransactionCustomPeer(
|
||||
transaction.peer,
|
||||
transaction.amount.currency === TON_CURRENCY_CODE,
|
||||
);
|
||||
title = customPeer.title || oldLang(customPeer.titleKey!);
|
||||
description = oldLang(customPeer.subtitleKey!);
|
||||
avatarPeer = customPeer;
|
||||
@ -178,11 +182,11 @@ const StarsTransactionItem = ({ transaction, className }: OwnProps) => {
|
||||
</div>
|
||||
<div className={styles.stars}>
|
||||
<span
|
||||
className={buildClassName(styles.amount, isNegativeStarsAmount(stars) ? styles.negative : styles.positive)}
|
||||
className={buildClassName(styles.amount, isNegativeAmount(amount) ? styles.negative : styles.positive)}
|
||||
>
|
||||
{formatStarsTransactionAmount(lang, stars)}
|
||||
{formatStarsTransactionAmount(lang, amount)}
|
||||
</span>
|
||||
<StarIcon className={styles.star} type="gold" size="adaptive" />
|
||||
{amount.currency === STARS_CURRENCY_CODE && <StarIcon className={styles.star} type="gold" size="adaptive" />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -9,6 +9,7 @@ import type {
|
||||
import type { TabState } from '../../../../global/types';
|
||||
import { MediaViewerOrigin } from '../../../../types';
|
||||
|
||||
import { STARS_CURRENCY_CODE } from '../../../../config';
|
||||
import { getMessageLink } from '../../../../global/helpers';
|
||||
import {
|
||||
buildStarsTransactionCustomPeer,
|
||||
@ -17,6 +18,7 @@ import {
|
||||
import {
|
||||
selectCanPlayAnimatedEmojis,
|
||||
selectGiftStickerForStars,
|
||||
selectGiftStickerForTon,
|
||||
selectPeer,
|
||||
} from '../../../../global/selectors';
|
||||
import buildClassName from '../../../../util/buildClassName';
|
||||
@ -25,7 +27,7 @@ import { formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
||||
import { formatStarsAsIcon } from '../../../../util/localization/format';
|
||||
import { formatPercent } from '../../../../util/textFormat';
|
||||
import { getGiftAttributes, getStickerFromGift } from '../../../common/helpers/gifts';
|
||||
import { getTransactionTitle, isNegativeStarsAmount } from '../helpers/transaction';
|
||||
import { getTransactionTitle, isNegativeAmount } from '../helpers/transaction';
|
||||
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
@ -85,7 +87,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
const {
|
||||
giveawayPostId, photo, stars, isGiftUpgrade, starGift, isGiftResale,
|
||||
giveawayPostId, photo, amount, isGiftUpgrade, starGift, isGiftResale,
|
||||
starRefCommision,
|
||||
} = transaction;
|
||||
|
||||
@ -132,7 +134,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
modelAttribute={giftAttributes!.model!}
|
||||
title={gift.title}
|
||||
subtitle={lang('GiftInfoCollectible', { number: gift.number })}
|
||||
resellPrice={transaction.stars}
|
||||
resellPrice={transaction.amount}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -169,11 +171,11 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
<p className={styles.description}>{description}</p>
|
||||
<p className={styles.amount}>
|
||||
<span
|
||||
className={buildClassName(styles.amount, isNegativeStarsAmount(stars) ? styles.negative : styles.positive)}
|
||||
className={buildClassName(styles.amount, isNegativeAmount(amount) ? styles.negative : styles.positive)}
|
||||
>
|
||||
{formatStarsTransactionAmount(lang, stars)}
|
||||
{formatStarsTransactionAmount(lang, amount)}
|
||||
</span>
|
||||
<StarIcon type="gold" size="middle" />
|
||||
{amount.currency === STARS_CURRENCY_CODE && <StarIcon type="gold" size="middle" />}
|
||||
{transaction.isRefund && (
|
||||
<p className={styles.refunded}>{lang('Refunded')}</p>
|
||||
)}
|
||||
@ -212,7 +214,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
if (isGiftResale) {
|
||||
tableData.push([
|
||||
oldLang('StarGiftReason'),
|
||||
isNegativeStarsAmount(transaction.stars)
|
||||
isNegativeAmount(transaction.amount)
|
||||
? lang('StarGiftSaleTransaction')
|
||||
: lang('StarGiftPurchaseTransaction'),
|
||||
]);
|
||||
@ -221,7 +223,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
let peerLabel;
|
||||
if (isGiftUpgrade) {
|
||||
peerLabel = oldLang('Stars.Transaction.GiftFrom');
|
||||
} else if (isNegativeStarsAmount(stars) || transaction.isMyGift) {
|
||||
} else if (isNegativeAmount(amount) || transaction.isMyGift) {
|
||||
peerLabel = oldLang('Stars.Transaction.To');
|
||||
} else if (transaction.starRefCommision && !transaction.paidMessages && !isGiftResale) {
|
||||
peerLabel = oldLang('StarsTransaction.StarRefReason.Miniapp');
|
||||
@ -240,7 +242,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
tableData.push([
|
||||
lang('PaidMessageTransactionTotal'),
|
||||
formatStarsAsIcon(lang,
|
||||
transaction.stars.amount / ((100 - transaction.starRefCommision) / 100),
|
||||
transaction.amount.amount / ((100 - transaction.starRefCommision) / 100),
|
||||
{ asFont: false, className: styles.starIcon, containerClassName: styles.totalStars }),
|
||||
]);
|
||||
}
|
||||
@ -249,9 +251,9 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
tableData.push([oldLang('Stars.Transaction.Reaction.Post'), <SafeLink url={messageLink} text={messageLink} />]);
|
||||
}
|
||||
|
||||
if (giveawayMessageLink) {
|
||||
if (giveawayMessageLink && transaction.amount.currency === STARS_CURRENCY_CODE) {
|
||||
tableData.push([oldLang('BoostReason'), <SafeLink url={giveawayMessageLink} text={oldLang('Giveaway')} />]);
|
||||
tableData.push([oldLang('Gift'), oldLang('Stars', transaction.stars, 'i')]);
|
||||
tableData.push([oldLang('Gift'), oldLang('Stars', transaction.amount, 'i')]);
|
||||
}
|
||||
|
||||
if (transaction.id) {
|
||||
@ -322,8 +324,10 @@ export default memo(withGlobal<OwnProps>(
|
||||
const peer = peerId ? selectPeer(global, peerId) : undefined;
|
||||
const paidMessageCommission = global.appConfig?.starsPaidMessageCommissionPermille;
|
||||
|
||||
const starCount = modal?.transaction.stars;
|
||||
const starsGiftSticker = modal?.transaction.isGift && selectGiftStickerForStars(global, starCount?.amount);
|
||||
const currencyAmount = modal?.transaction.amount;
|
||||
const starsGiftSticker = modal?.transaction.isGift
|
||||
&& currencyAmount?.currency === STARS_CURRENCY_CODE ? selectGiftStickerForStars(global, currencyAmount?.amount)
|
||||
: selectGiftStickerForTon(global, currencyAmount?.amount);
|
||||
|
||||
return {
|
||||
peer,
|
||||
|
||||
@ -53,3 +53,14 @@
|
||||
.offerButton {
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.currencySelector {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.currencyIcon {
|
||||
margin-inline-end: 0.25rem;
|
||||
}
|
||||
|
||||
@ -4,22 +4,31 @@ import {
|
||||
useState } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiDraft, ApiStarsAmount } from '../../../api/types';
|
||||
import type { ApiDraft, ApiStarsAmount, ApiTypeCurrencyAmount } from '../../../api/types';
|
||||
import type { ApiPeer } from '../../../api/types';
|
||||
import type { TabState } from '../../../global/types';
|
||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||
|
||||
import {
|
||||
STARS_CURRENCY_CODE,
|
||||
STARS_SUGGESTED_POST_AGE_MIN,
|
||||
STARS_SUGGESTED_POST_AMOUNT_MAX,
|
||||
STARS_SUGGESTED_POST_AMOUNT_MIN,
|
||||
STARS_SUGGESTED_POST_FUTURE_MAX,
|
||||
STARS_SUGGESTED_POST_FUTURE_MIN } from '../../../config';
|
||||
import { selectPeer } from '../../../global/selectors';
|
||||
STARS_SUGGESTED_POST_FUTURE_MIN,
|
||||
TON_CURRENCY_CODE,
|
||||
TON_SUGGESTED_POST_AMOUNT_MAX,
|
||||
TON_SUGGESTED_POST_AMOUNT_MIN } from '../../../config';
|
||||
import { selectIsMonoforumAdmin, selectPeer } from '../../../global/selectors';
|
||||
import { selectDraft } from '../../../global/selectors/messages';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatScheduledDateTime, formatShortDuration } from '../../../util/dates/dateFormat';
|
||||
import { formatStarsAsIcon, formatStarsAsText } from '../../../util/localization/format';
|
||||
import { convertTonFromNanos, convertTonToNanos } from '../../../util/formatCurrency';
|
||||
import {
|
||||
formatStarsAsIcon,
|
||||
formatStarsAsText,
|
||||
formatTonAsIcon,
|
||||
formatTonAsText } from '../../../util/localization/format';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
@ -41,25 +50,33 @@ import useFlag from '../../../hooks/useFlag';
|
||||
|
||||
type StateProps = {
|
||||
starBalance?: ApiStarsAmount;
|
||||
tonBalance?: number;
|
||||
peer?: ApiPeer;
|
||||
currentDraft?: ApiDraft;
|
||||
maxAmount: number;
|
||||
minAmount: number;
|
||||
maxStarsAmount: number;
|
||||
minStarsAmount: number;
|
||||
tonMaxAmount: number;
|
||||
tonMinAmount: number;
|
||||
ageMinSeconds: number;
|
||||
futureMin: number;
|
||||
futureMax: number;
|
||||
isMonoforumAdmin?: boolean;
|
||||
};
|
||||
|
||||
const SuggestMessageModal = ({
|
||||
modal,
|
||||
starBalance,
|
||||
tonBalance,
|
||||
peer,
|
||||
currentDraft,
|
||||
maxAmount,
|
||||
minAmount,
|
||||
maxStarsAmount,
|
||||
minStarsAmount,
|
||||
tonMaxAmount,
|
||||
tonMinAmount,
|
||||
ageMinSeconds,
|
||||
futureMin,
|
||||
futureMax,
|
||||
isMonoforumAdmin,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { closeSuggestMessageModal, updateDraftSuggestedPostInfo, openStarsBalanceModal } = getActions();
|
||||
const [isCalendarOpened, openCalendar, closeCalendar] = useFlag();
|
||||
@ -68,9 +85,12 @@ const SuggestMessageModal = ({
|
||||
const currentReplyInfo = currentDraft?.replyInfo;
|
||||
const isInSuggestChangesMode = Boolean(currentReplyInfo);
|
||||
|
||||
const [starsAmount, setStarsAmount] = useState<number | undefined>(
|
||||
const [currencyAmount, setCurrencyAmount] = useState<number | undefined>(
|
||||
currentSuggestedPostInfo?.price?.amount || undefined,
|
||||
);
|
||||
const [selectedCurrency, setSelectedCurrency] = useState<ApiTypeCurrencyAmount['currency']>(
|
||||
currentSuggestedPostInfo?.price?.currency || STARS_CURRENCY_CODE,
|
||||
);
|
||||
const [scheduleDate, setScheduleDate] = useState<number | undefined>(
|
||||
currentSuggestedPostInfo?.scheduleDate
|
||||
? currentSuggestedPostInfo.scheduleDate * 1000
|
||||
@ -78,7 +98,10 @@ const SuggestMessageModal = ({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setStarsAmount(currentSuggestedPostInfo?.price?.amount || undefined);
|
||||
const price = currentSuggestedPostInfo?.price;
|
||||
const amount = price?.currency === TON_CURRENCY_CODE ? convertTonFromNanos(price.amount) : price?.amount;
|
||||
setCurrencyAmount(amount);
|
||||
setSelectedCurrency(currentSuggestedPostInfo?.price?.currency || STARS_CURRENCY_CODE);
|
||||
setScheduleDate(currentSuggestedPostInfo?.scheduleDate
|
||||
? currentSuggestedPostInfo.scheduleDate * 1000
|
||||
: undefined);
|
||||
@ -87,6 +110,7 @@ const SuggestMessageModal = ({
|
||||
const lang = useLang();
|
||||
const oldLang = useOldLang();
|
||||
|
||||
const isCurrencyStars = selectedCurrency === STARS_CURRENCY_CODE;
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const minAt = (now + futureMin) * 1000;
|
||||
const maxAt = (now + futureMax) * 1000;
|
||||
@ -97,9 +121,9 @@ const SuggestMessageModal = ({
|
||||
const number = parseFloat(value);
|
||||
|
||||
const result = value === '' || Number.isNaN(number) ? undefined
|
||||
: Math.min(Math.max(number, 0), maxAmount);
|
||||
: Math.min(Math.max(number, 0), currentMaxAmount);
|
||||
|
||||
setStarsAmount(result);
|
||||
setCurrencyAmount(result);
|
||||
});
|
||||
|
||||
const handleExpireDateChange = useLastCallback((date: Date) => {
|
||||
@ -112,28 +136,44 @@ const SuggestMessageModal = ({
|
||||
closeCalendar();
|
||||
});
|
||||
|
||||
const isDisabled = Boolean(starsAmount) && starsAmount < minAmount;
|
||||
const currentMinAmount = isCurrencyStars ? minStarsAmount : convertTonFromNanos(tonMinAmount);
|
||||
const currentMaxAmount = isCurrencyStars ? maxStarsAmount : convertTonFromNanos(tonMaxAmount);
|
||||
const isDisabled = Boolean(currencyAmount) && currencyAmount < currentMinAmount;
|
||||
|
||||
const handleOffer = useLastCallback(() => {
|
||||
const neededAmount = starsAmount || 0;
|
||||
const neededAmount = currencyAmount
|
||||
? (isCurrencyStars ? currencyAmount : convertTonToNanos(currencyAmount))
|
||||
: 0;
|
||||
|
||||
if (isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentBalance = starBalance?.amount || 0;
|
||||
if (!isMonoforumAdmin) {
|
||||
if (isCurrencyStars) {
|
||||
const currentBalance = starBalance?.amount || 0;
|
||||
|
||||
if (neededAmount > currentBalance) {
|
||||
openStarsBalanceModal({
|
||||
topup: {
|
||||
balanceNeeded: neededAmount,
|
||||
},
|
||||
});
|
||||
return;
|
||||
if (neededAmount > currentBalance) {
|
||||
openStarsBalanceModal({
|
||||
topup: {
|
||||
balanceNeeded: neededAmount,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const currentTonBalance = tonBalance || 0;
|
||||
if (neededAmount > currentTonBalance) {
|
||||
openStarsBalanceModal({
|
||||
currency: TON_CURRENCY_CODE,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateDraftSuggestedPostInfo({
|
||||
price: { amount: neededAmount, nanos: 0 },
|
||||
price: { currency: selectedCurrency, amount: neededAmount, nanos: 0 },
|
||||
scheduleDate: scheduleDate ? scheduleDate / 1000 : undefined,
|
||||
});
|
||||
|
||||
@ -153,23 +193,51 @@ const SuggestMessageModal = ({
|
||||
>
|
||||
<div className={styles.form}>
|
||||
<div className={styles.section}>
|
||||
<div className={styles.currencySelector}>
|
||||
<Button
|
||||
className={styles.currencyButton}
|
||||
color={isCurrencyStars ? 'primary' : 'translucent'}
|
||||
pill
|
||||
fluid
|
||||
size="tiny"
|
||||
noFastClick
|
||||
onClick={() => setSelectedCurrency(STARS_CURRENCY_CODE)}
|
||||
>
|
||||
<Icon name="star" className={styles.currencyIcon} />
|
||||
{lang('CurrencyStars')}
|
||||
</Button>
|
||||
<Button
|
||||
className={styles.currencyButton}
|
||||
fluid
|
||||
color={!isCurrencyStars ? 'primary' : 'translucent'}
|
||||
pill
|
||||
size="tiny"
|
||||
noFastClick
|
||||
onClick={() => setSelectedCurrency(TON_CURRENCY_CODE)}
|
||||
>
|
||||
<Icon name="toncoin" className={styles.currencyIcon} />
|
||||
{lang('CurrencyTon')}
|
||||
</Button>
|
||||
</div>
|
||||
<InputText
|
||||
label={lang('InputPlaceholderPrice')}
|
||||
className={buildClassName(styles.input)}
|
||||
value={starsAmount?.toString()}
|
||||
value={currencyAmount?.toString()}
|
||||
onChange={handleAmountChange}
|
||||
inputMode="numeric"
|
||||
tabIndex={0}
|
||||
teactExperimentControlled
|
||||
teactExperimentControlled={isCurrencyStars}
|
||||
/>
|
||||
<div className={styles.description}>
|
||||
{starsAmount !== undefined && starsAmount > 0 && starsAmount < minAmount
|
||||
{currencyAmount !== undefined && currencyAmount > 0 && currencyAmount < currentMinAmount
|
||||
? lang('DescriptionSuggestedPostMinimumOffer', {
|
||||
amount: formatStarsAsText(lang, minAmount) },
|
||||
amount: isCurrencyStars
|
||||
? formatStarsAsText(lang, currentMinAmount)
|
||||
: formatTonAsText(lang, currentMinAmount) },
|
||||
{ withNodes: true, withMarkdown: true })
|
||||
: lang('SuggestMessagePriceDescription', {
|
||||
currency: lang('CurrencyStars'),
|
||||
})}
|
||||
: isCurrencyStars
|
||||
? lang('SuggestMessagePriceDescriptionStars')
|
||||
: lang('SuggestMessagePriceDescriptionTon')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -178,7 +246,9 @@ const SuggestMessageModal = ({
|
||||
<input
|
||||
type="text"
|
||||
className={buildClassName('form-control', isCalendarOpened && 'focus')}
|
||||
value={scheduleDate ? formatScheduledDateTime(scheduleDate / 1000, lang, oldLang) : lang('TitleAnytime')}
|
||||
value={scheduleDate
|
||||
? formatScheduledDateTime(scheduleDate / 1000, lang, oldLang)
|
||||
: lang('SuggestMessageAnytime')}
|
||||
autoComplete="off"
|
||||
onClick={openCalendar}
|
||||
onFocus={openCalendar}
|
||||
@ -205,7 +275,7 @@ const SuggestMessageModal = ({
|
||||
onSubmit={handleExpireDateChange}
|
||||
selectedAt={scheduleDate || defaultSelectedTime}
|
||||
submitButtonLabel={lang('Save')}
|
||||
secondButtonLabel={lang('TitleAnytime')}
|
||||
secondButtonLabel={lang('SuggestMessageAnytime')}
|
||||
onSecondButtonClick={handleAnytimeClick}
|
||||
description={lang('SuggestMessageDateTimeHint')}
|
||||
/>
|
||||
@ -217,8 +287,10 @@ const SuggestMessageModal = ({
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{isInSuggestChangesMode ? lang('ButtonUpdateTerms')
|
||||
: starsAmount ? lang('ButtonOfferAmount', {
|
||||
amount: formatStarsAsIcon(lang, starsAmount, { asFont: true }),
|
||||
: currencyAmount ? lang('ButtonOfferAmount', {
|
||||
amount: isCurrencyStars
|
||||
? formatStarsAsIcon(lang, currencyAmount, { asFont: true })
|
||||
: formatTonAsIcon(lang, currencyAmount, { asFont: true }),
|
||||
}, {
|
||||
withNodes: true,
|
||||
withMarkdown: true,
|
||||
@ -236,21 +308,30 @@ export default memo(withGlobal<OwnProps>(
|
||||
const currentDraft = modal ? selectDraft(global, modal.chatId, MAIN_THREAD_ID) : undefined;
|
||||
|
||||
const { appConfig } = global;
|
||||
const maxAmount = appConfig?.starsSuggestedPostAmountMax || STARS_SUGGESTED_POST_AMOUNT_MAX;
|
||||
const minAmount = appConfig?.starsSuggestedPostAmountMin || STARS_SUGGESTED_POST_AMOUNT_MIN;
|
||||
const maxStarsAmount = appConfig?.starsSuggestedPostAmountMax || STARS_SUGGESTED_POST_AMOUNT_MAX;
|
||||
const minStarsAmount = appConfig?.starsSuggestedPostAmountMin || STARS_SUGGESTED_POST_AMOUNT_MIN;
|
||||
const ageMinSeconds = appConfig?.starsSuggestedPostAgeMin || STARS_SUGGESTED_POST_AGE_MIN;
|
||||
const futureMin = appConfig?.starsSuggestedPostFutureMin || STARS_SUGGESTED_POST_FUTURE_MIN;
|
||||
const futureMax = appConfig?.starsSuggestedPostFutureMax || STARS_SUGGESTED_POST_FUTURE_MAX;
|
||||
|
||||
const tonMaxAmount = appConfig?.tonSuggestedPostAmountMax || TON_SUGGESTED_POST_AMOUNT_MAX;
|
||||
const tonMinAmount = appConfig?.tonSuggestedPostAmountMin || TON_SUGGESTED_POST_AMOUNT_MIN;
|
||||
|
||||
const isMonoforumAdmin = modal ? selectIsMonoforumAdmin(global, modal.chatId) : false;
|
||||
|
||||
return {
|
||||
peer,
|
||||
starBalance,
|
||||
tonBalance: global.ton?.balance?.amount,
|
||||
currentDraft,
|
||||
maxAmount,
|
||||
minAmount,
|
||||
maxStarsAmount,
|
||||
minStarsAmount,
|
||||
tonMaxAmount,
|
||||
tonMinAmount,
|
||||
ageMinSeconds,
|
||||
futureMin,
|
||||
futureMax,
|
||||
isMonoforumAdmin,
|
||||
};
|
||||
},
|
||||
)(SuggestMessageModal));
|
||||
|
||||
@ -4,15 +4,18 @@ import { getActions, withGlobal } from '../../../global';
|
||||
import type { ApiMessage, ApiPeer } from '../../../api/types';
|
||||
import type { TabState } from '../../../global/types';
|
||||
|
||||
import { STARS_SUGGESTED_POST_AGE_MIN,
|
||||
import { STARS_CURRENCY_CODE, STARS_SUGGESTED_POST_AGE_MIN,
|
||||
STARS_SUGGESTED_POST_COMMISSION_PERMILLE,
|
||||
STARS_SUGGESTED_POST_FUTURE_MAX,
|
||||
STARS_SUGGESTED_POST_FUTURE_MIN,
|
||||
TON_CURRENCY_CODE,
|
||||
TON_SUGGESTED_POST_COMMISSION_PERMILLE,
|
||||
} from '../../../config';
|
||||
import { getPeerFullTitle } from '../../../global/helpers/peers';
|
||||
import { selectChatMessage, selectIsMonoforumAdmin, selectSender } from '../../../global/selectors';
|
||||
import { formatScheduledDateTime, formatShortDuration } from '../../../util/dates/dateFormat';
|
||||
import { formatStarsAsText } from '../../../util/localization/format';
|
||||
import { convertTonFromNanos } from '../../../util/formatCurrency';
|
||||
import { formatStarsAsText, formatTonAsText } from '../../../util/localization/format';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
@ -31,6 +34,7 @@ export type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
commissionPermille: number;
|
||||
tonCommissionPermille: number;
|
||||
minAge: number;
|
||||
futureMin: number;
|
||||
futureMax: number;
|
||||
@ -46,6 +50,7 @@ const SuggestedPostApprovalModal = ({
|
||||
sender,
|
||||
isAdmin,
|
||||
commissionPermille,
|
||||
tonCommissionPermille,
|
||||
minAge,
|
||||
futureMin,
|
||||
futureMax,
|
||||
@ -114,7 +119,10 @@ const SuggestedPostApprovalModal = ({
|
||||
const senderName = sender ? getPeerFullTitle(oldLang, sender) : '';
|
||||
|
||||
const renderContent = () => {
|
||||
const amount = message?.suggestedPostInfo?.price?.amount;
|
||||
const price = message?.suggestedPostInfo?.price;
|
||||
const amount = price?.amount;
|
||||
const currency = price?.currency || STARS_CURRENCY_CODE;
|
||||
|
||||
const question = lang(
|
||||
'SuggestedPostConfirmMessage',
|
||||
{ peer: senderName },
|
||||
@ -125,10 +133,13 @@ const SuggestedPostApprovalModal = ({
|
||||
return questionText;
|
||||
}
|
||||
|
||||
const commission = (commissionPermille / 10);
|
||||
const currentCommissionPermille = currency === TON_CURRENCY_CODE ? tonCommissionPermille : commissionPermille;
|
||||
const commission = (currentCommissionPermille / 10);
|
||||
const amountWithCommission = amount / 100 * commission;
|
||||
|
||||
const starsText = formatStarsAsText(lang, amountWithCommission);
|
||||
const formattedAmount = currency === TON_CURRENCY_CODE
|
||||
? formatTonAsText(lang, convertTonFromNanos(amountWithCommission))
|
||||
: formatStarsAsText(lang, amountWithCommission);
|
||||
|
||||
const ageMinSeconds = minAge;
|
||||
const duration = formatShortDuration(lang, ageMinSeconds, true);
|
||||
@ -146,7 +157,7 @@ const SuggestedPostApprovalModal = ({
|
||||
</div>
|
||||
<div className={styles.details}>
|
||||
{renderText(lang(key, {
|
||||
amount: starsText,
|
||||
amount: formattedAmount,
|
||||
commission,
|
||||
duration,
|
||||
time,
|
||||
@ -165,7 +176,7 @@ const SuggestedPostApprovalModal = ({
|
||||
</div>
|
||||
<div className={styles.details}>
|
||||
{renderText(lang(key, {
|
||||
amount: starsText,
|
||||
amount: formattedAmount,
|
||||
commission,
|
||||
duration,
|
||||
}, { withNodes: true, withMarkdown: true }))}
|
||||
@ -215,6 +226,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
const { appConfig } = global;
|
||||
const commissionPermille = appConfig?.starsSuggestedPostCommissionPermille
|
||||
|| STARS_SUGGESTED_POST_COMMISSION_PERMILLE;
|
||||
const tonCommissionPermille = appConfig?.tonSuggestedPostCommissionPermille
|
||||
|| TON_SUGGESTED_POST_COMMISSION_PERMILLE;
|
||||
const minAge = appConfig?.starsSuggestedPostAgeMin || STARS_SUGGESTED_POST_AGE_MIN;
|
||||
const futureMin = (appConfig?.starsSuggestedPostFutureMin || STARS_SUGGESTED_POST_FUTURE_MIN) * 2;
|
||||
const futureMax = appConfig?.starsSuggestedPostFutureMax || STARS_SUGGESTED_POST_FUTURE_MAX;
|
||||
@ -228,6 +241,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
sender,
|
||||
isAdmin,
|
||||
commissionPermille,
|
||||
tonCommissionPermille,
|
||||
scheduleDate,
|
||||
};
|
||||
},
|
||||
|
||||
@ -117,7 +117,12 @@ export const STARS_SUGGESTED_POST_COMMISSION_PERMILLE = 850;
|
||||
export const STARS_SUGGESTED_POST_AGE_MIN = 86400; // 24 hours in seconds
|
||||
export const STARS_SUGGESTED_POST_FUTURE_MAX = 2678400; // 31 days in seconds
|
||||
export const STARS_SUGGESTED_POST_FUTURE_MIN = 300; // 5 minutes in seconds
|
||||
export const TON_CURRENCY_CODE = 'TON';
|
||||
export const TON_SUGGESTED_POST_COMMISSION_PERMILLE = 850;
|
||||
export const TON_USD_RATE_DEFAULT = 3;
|
||||
export const TON_TOPUP_URL_DEFAULT = 'https://fragment.com/ads/topup';
|
||||
export const TON_SUGGESTED_POST_AMOUNT_MIN = 10000000; // 0.01 TON in nanos
|
||||
export const TON_SUGGESTED_POST_AMOUNT_MAX = 10000000000000; // 10 000 TON in nanos
|
||||
|
||||
export const STORY_VIEWS_MIN_SEARCH = 15;
|
||||
export const STORY_MIN_REACTIONS_SORT = 10;
|
||||
|
||||
@ -33,10 +33,12 @@ import {
|
||||
MESSAGE_LIST_SLICE,
|
||||
RE_TELEGRAM_LINK,
|
||||
SERVICE_NOTIFICATIONS_USER_ID,
|
||||
STARS_CURRENCY_CODE,
|
||||
STARS_SUGGESTED_POST_FUTURE_MIN,
|
||||
SUPPORTED_AUDIO_CONTENT_TYPES,
|
||||
SUPPORTED_PHOTO_CONTENT_TYPES,
|
||||
SUPPORTED_VIDEO_CONTENT_TYPES,
|
||||
TON_CURRENCY_CODE,
|
||||
} from '../../../config';
|
||||
import { ensureProtocol, isMixedScriptUrl } from '../../../util/browser/url';
|
||||
import { IS_IOS } from '../../../util/browser/windowEnvironment';
|
||||
@ -361,18 +363,31 @@ addActionHandler('sendMessage', async (global, actions, payload): Promise<void>
|
||||
|
||||
const messagePriceInStars = await getPeerStarsForMessage(global, chatId!);
|
||||
|
||||
const suggestedPostPrice = draftSuggestedPostInfo?.price?.amount || 0;
|
||||
if (suggestedPostPrice && !draftReplyInfo) {
|
||||
const currentBalance = global.stars?.balance?.amount || 0;
|
||||
const suggestedPostPrice = draftSuggestedPostInfo?.price;
|
||||
const suggestedPostCurrency = suggestedPostPrice?.currency || STARS_CURRENCY_CODE;
|
||||
const suggestedPostAmount = suggestedPostPrice?.amount || 0;
|
||||
if (suggestedPostAmount && !draftReplyInfo) {
|
||||
if (suggestedPostCurrency === STARS_CURRENCY_CODE) {
|
||||
const currentBalance = global.stars?.balance?.amount || 0;
|
||||
|
||||
if (suggestedPostPrice > currentBalance) {
|
||||
actions.openStarsBalanceModal({
|
||||
topup: {
|
||||
balanceNeeded: suggestedPostPrice,
|
||||
},
|
||||
tabId,
|
||||
});
|
||||
return;
|
||||
if (suggestedPostAmount > currentBalance) {
|
||||
actions.openStarsBalanceModal({
|
||||
topup: {
|
||||
balanceNeeded: suggestedPostAmount,
|
||||
},
|
||||
tabId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (suggestedPostCurrency === TON_CURRENCY_CODE) {
|
||||
const currentTonBalance = global.ton?.balance?.amount || 0;
|
||||
if (suggestedPostAmount > currentTonBalance) {
|
||||
actions.openStarsBalanceModal({
|
||||
currency: TON_CURRENCY_CODE,
|
||||
tabId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2203,16 +2218,28 @@ addActionHandler('approveSuggestedPost', async (global, actions, payload): Promi
|
||||
|
||||
if (!isAdmin && message?.suggestedPostInfo?.price?.amount) {
|
||||
const neededAmount = message.suggestedPostInfo.price.amount;
|
||||
const currentBalance = global.stars?.balance?.amount || 0;
|
||||
const isCurrencyStars = message.suggestedPostInfo.price.currency === STARS_CURRENCY_CODE;
|
||||
|
||||
if (neededAmount > currentBalance) {
|
||||
actions.openStarsBalanceModal({
|
||||
topup: {
|
||||
balanceNeeded: neededAmount,
|
||||
},
|
||||
tabId,
|
||||
});
|
||||
return;
|
||||
if (isCurrencyStars) {
|
||||
const currentBalance = global.stars?.balance?.amount || 0;
|
||||
if (neededAmount > currentBalance) {
|
||||
actions.openStarsBalanceModal({
|
||||
topup: {
|
||||
balanceNeeded: neededAmount,
|
||||
},
|
||||
tabId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const currentTonBalance = global.ton?.balance?.amount || 0;
|
||||
if (neededAmount > currentTonBalance) {
|
||||
actions.openStarsBalanceModal({
|
||||
currency: TON_CURRENCY_CODE,
|
||||
tabId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,12 @@ import type { ApiSavedStarGift, ApiStarGiftUnique } from '../../../api/types';
|
||||
import type { StarGiftCategory } from '../../../types';
|
||||
import type { ActionReturnType } from '../../types';
|
||||
|
||||
import { DEFAULT_RESALE_GIFTS_FILTER_OPTIONS, RESALE_GIFTS_LIMIT } from '../../../config';
|
||||
import {
|
||||
DEFAULT_RESALE_GIFTS_FILTER_OPTIONS,
|
||||
RESALE_GIFTS_LIMIT,
|
||||
STARS_CURRENCY_CODE,
|
||||
TON_CURRENCY_CODE,
|
||||
} from '../../../config';
|
||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
@ -26,57 +31,82 @@ import {
|
||||
} from '../../selectors';
|
||||
|
||||
addActionHandler('loadStarStatus', async (global): Promise<void> => {
|
||||
const currentStatus = global.stars;
|
||||
const needsTopupOptions = !currentStatus?.topupOptions;
|
||||
const currentStarsStatus = global.stars;
|
||||
const needsTopupOptions = !currentStarsStatus?.topupOptions;
|
||||
|
||||
const [status, topupOptions] = await Promise.all([
|
||||
const [starsStatus, tonStatus, topupOptions] = await Promise.all([
|
||||
callApi('fetchStarsStatus'),
|
||||
callApi('fetchStarsStatus', { isTon: true }),
|
||||
needsTopupOptions ? callApi('fetchStarsTopupOptions') : undefined,
|
||||
]);
|
||||
|
||||
if (!status || (needsTopupOptions && !topupOptions)) {
|
||||
if (!(starsStatus || tonStatus) || (needsTopupOptions && !topupOptions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
global = getGlobal();
|
||||
|
||||
global = {
|
||||
...global,
|
||||
stars: {
|
||||
...currentStatus,
|
||||
balance: status.balance,
|
||||
topupOptions: topupOptions || currentStatus!.topupOptions,
|
||||
history: {
|
||||
all: undefined,
|
||||
inbound: undefined,
|
||||
outbound: undefined,
|
||||
if (starsStatus && starsStatus.balance.currency === STARS_CURRENCY_CODE) {
|
||||
global = {
|
||||
...global,
|
||||
stars: {
|
||||
...currentStarsStatus,
|
||||
balance: starsStatus.balance,
|
||||
topupOptions: topupOptions || currentStarsStatus!.topupOptions,
|
||||
history: {
|
||||
all: undefined,
|
||||
inbound: undefined,
|
||||
outbound: undefined,
|
||||
},
|
||||
subscriptions: undefined,
|
||||
},
|
||||
subscriptions: undefined,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
if (status.history) {
|
||||
global = appendStarsTransactions(global, 'all', status.history, status.nextHistoryOffset);
|
||||
if (starsStatus.history) {
|
||||
global = appendStarsTransactions(global, 'all', starsStatus.history, starsStatus.nextHistoryOffset);
|
||||
}
|
||||
|
||||
if (starsStatus.subscriptions) {
|
||||
global = appendStarsSubscriptions(global, starsStatus.subscriptions, starsStatus.nextSubscriptionOffset);
|
||||
}
|
||||
}
|
||||
|
||||
if (status.subscriptions) {
|
||||
global = appendStarsSubscriptions(global, status.subscriptions, status.nextSubscriptionOffset);
|
||||
if (tonStatus?.balance.currency === TON_CURRENCY_CODE) {
|
||||
global = {
|
||||
...global,
|
||||
ton: {
|
||||
...tonStatus,
|
||||
balance: tonStatus.balance,
|
||||
history: {
|
||||
all: undefined,
|
||||
inbound: undefined,
|
||||
outbound: undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
global = updateStarsBalance(global, tonStatus.balance);
|
||||
|
||||
if (tonStatus.history) {
|
||||
global = appendStarsTransactions(global, 'all', tonStatus.history, tonStatus.nextHistoryOffset, true);
|
||||
}
|
||||
}
|
||||
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('loadStarsTransactions', async (global, actions, payload): Promise<void> => {
|
||||
const { type } = payload;
|
||||
const { type, isTon } = payload;
|
||||
|
||||
const history = global.stars?.history[type];
|
||||
const history = isTon ? global.ton?.history[type] : global.stars?.history[type];
|
||||
const offset = history?.nextOffset;
|
||||
if (history && !offset) return; // Already loaded all
|
||||
|
||||
const result = await callApi('fetchStarsTransactions', {
|
||||
isInbound: type === 'inbound' || undefined,
|
||||
isOutbound: type === 'outbound' || undefined,
|
||||
isInbound: type === 'inbound',
|
||||
isOutbound: type === 'outbound',
|
||||
offset: offset || '',
|
||||
isTon,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
@ -87,7 +117,7 @@ addActionHandler('loadStarsTransactions', async (global, actions, payload): Prom
|
||||
|
||||
global = updateStarsBalance(global, result.balance);
|
||||
if (result.history) {
|
||||
global = appendStarsTransactions(global, type, result.history, result.nextOffset);
|
||||
global = appendStarsTransactions(global, type, result.history, result.nextOffset, isTon);
|
||||
}
|
||||
setGlobal(global);
|
||||
});
|
||||
@ -315,7 +345,7 @@ addActionHandler('loadStarsSubscriptions', async (global): Promise<void> => {
|
||||
offset: offset || '',
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
if (!result || result.balance.currency !== STARS_CURRENCY_CODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -215,6 +215,22 @@ addActionHandler('loadPremiumGifts', async (global): Promise<void> => {
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('loadTonGifts', async (global): Promise<void> => {
|
||||
const stickerSet = await callApi('fetchTonGifts');
|
||||
if (!stickerSet) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { set, stickers } = stickerSet;
|
||||
|
||||
global = getGlobal();
|
||||
global = {
|
||||
...global,
|
||||
tonGifts: { ...set, stickers },
|
||||
};
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('loadDefaultTopicIcons', async (global): Promise<void> => {
|
||||
const stickerSet = await callApi('fetchDefaultTopicIcons');
|
||||
if (!stickerSet) {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { ApiInputSavedStarGift, ApiSavedStarGift } from '../../../api/types';
|
||||
import type { ActionReturnType } from '../../types';
|
||||
|
||||
import { STARS_CURRENCY_CODE } from '../../../config';
|
||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import * as langProvider from '../../../util/oldLangProvider';
|
||||
import { addTabStateResetterAction } from '../../helpers/meta';
|
||||
@ -110,6 +111,7 @@ addActionHandler('openStarsBalanceModal', (global, actions, payload): ActionRetu
|
||||
originGift,
|
||||
topup,
|
||||
shouldIgnoreBalance,
|
||||
currency = STARS_CURRENCY_CODE,
|
||||
tabId = getCurrentTabId(),
|
||||
} = payload || {};
|
||||
|
||||
@ -140,6 +142,7 @@ addActionHandler('openStarsBalanceModal', (global, actions, payload): ActionRetu
|
||||
originReaction,
|
||||
originGift,
|
||||
topup,
|
||||
currency,
|
||||
},
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
@ -8,12 +8,15 @@ import type {
|
||||
ApiStarsTransaction,
|
||||
ApiStarsTransactionPeer,
|
||||
ApiStarsTransactionPeerPeer,
|
||||
ApiTypeCurrencyAmount,
|
||||
} from '../../api/types';
|
||||
import type { CustomPeer } from '../../types';
|
||||
import type { LangFn } from '../../util/localization';
|
||||
import type { GlobalState } from '../types';
|
||||
|
||||
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../config';
|
||||
import arePropsShallowEqual from '../../util/arePropsShallowEqual';
|
||||
import { convertCurrencyFromBaseUnit } from '../../util/formatCurrency';
|
||||
import { selectChat, selectPeer, selectUser } from '../selectors';
|
||||
|
||||
export function getRequestInputInvoice<T extends GlobalState>(
|
||||
@ -257,6 +260,7 @@ export function getRequestInputSavedStarGift<T extends GlobalState>(
|
||||
|
||||
export function buildStarsTransactionCustomPeer(
|
||||
peer: Exclude<ApiStarsTransactionPeer, ApiStarsTransactionPeerPeer>,
|
||||
isForTon?: boolean,
|
||||
): CustomPeer {
|
||||
if (peer.type === 'appStore') {
|
||||
return {
|
||||
@ -279,8 +283,17 @@ export function buildStarsTransactionCustomPeer(
|
||||
}
|
||||
|
||||
if (peer.type === 'fragment') {
|
||||
if (isForTon) {
|
||||
return {
|
||||
avatarIcon: 'fragment',
|
||||
isCustomPeer: true,
|
||||
titleKey: 'Stars.Gift.Received.Title',
|
||||
subtitleKey: 'Stars.Intro.Transaction.Gift.UnknownUser',
|
||||
customPeerAvatarColor: '#000000',
|
||||
};
|
||||
}
|
||||
return {
|
||||
avatarIcon: 'star',
|
||||
avatarIcon: 'fragment',
|
||||
isCustomPeer: true,
|
||||
titleKey: 'Stars.Intro.Transaction.FragmentTopUp.Title',
|
||||
subtitleKey: 'Stars.Intro.Transaction.FragmentTopUp.Subtitle',
|
||||
@ -328,13 +341,29 @@ export function buildStarsTransactionCustomPeer(
|
||||
};
|
||||
}
|
||||
|
||||
export function formatStarsTransactionAmount(lang: LangFn, starsAmount: ApiStarsAmount) {
|
||||
const amount = starsAmount.amount + starsAmount.nanos / 1e9;
|
||||
if (amount < 0) {
|
||||
return `- ${lang.number(Math.abs(amount))}`;
|
||||
export function formatStarsTransactionAmount(lang: LangFn, currencyAmount: ApiTypeCurrencyAmount) {
|
||||
if (currencyAmount.currency === STARS_CURRENCY_CODE) {
|
||||
const amount = currencyAmount.amount + currencyAmount.nanos / 1e9;
|
||||
if (amount < 0) {
|
||||
return `- ${lang.number(Math.abs(amount))}`;
|
||||
}
|
||||
|
||||
return `+ ${lang.number(amount)}`;
|
||||
}
|
||||
|
||||
return `+ ${lang.number(amount)}`;
|
||||
if (currencyAmount.currency === TON_CURRENCY_CODE) {
|
||||
const amount = convertCurrencyFromBaseUnit(currencyAmount.amount, currencyAmount.currency);
|
||||
const absAmount = Math.abs(amount);
|
||||
const tonText = lang('TonAmountText', { amount: absAmount }, { pluralValue: absAmount });
|
||||
|
||||
if (amount < 0) {
|
||||
return `- ${tonText}`;
|
||||
}
|
||||
|
||||
return `+ ${tonText}`;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function formatStarsAmount(lang: LangFn, starsAmount: ApiStarsAmount) {
|
||||
@ -344,24 +373,45 @@ export function formatStarsAmount(lang: LangFn, starsAmount: ApiStarsAmount) {
|
||||
export function getStarsTransactionFromGift(message: ApiMessage): ApiStarsTransaction | undefined {
|
||||
const { action } = message.content;
|
||||
|
||||
if (action?.type !== 'giftStars') return undefined;
|
||||
if (action?.type === 'giftStars') {
|
||||
const { transactionId, stars } = action;
|
||||
|
||||
const { transactionId, stars } = action;
|
||||
return {
|
||||
id: transactionId,
|
||||
amount: {
|
||||
currency: STARS_CURRENCY_CODE,
|
||||
amount: stars,
|
||||
nanos: 0,
|
||||
},
|
||||
peer: {
|
||||
type: 'peer',
|
||||
id: message.isOutgoing ? message.chatId : (message.senderId || message.chatId),
|
||||
},
|
||||
date: message.date,
|
||||
isGift: true,
|
||||
isMyGift: message.isOutgoing || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id: transactionId,
|
||||
stars: {
|
||||
amount: stars,
|
||||
nanos: 0,
|
||||
},
|
||||
peer: {
|
||||
type: 'peer',
|
||||
id: message.isOutgoing ? message.chatId : (message.senderId || message.chatId),
|
||||
},
|
||||
date: message.date,
|
||||
isGift: true,
|
||||
isMyGift: message.isOutgoing || undefined,
|
||||
};
|
||||
if (action?.type === 'giftTon') {
|
||||
const { transactionId, cryptoAmount } = action;
|
||||
|
||||
return {
|
||||
id: transactionId,
|
||||
amount: {
|
||||
currency: TON_CURRENCY_CODE,
|
||||
amount: cryptoAmount,
|
||||
},
|
||||
peer: {
|
||||
type: 'fragment',
|
||||
},
|
||||
date: message.date,
|
||||
isGift: true,
|
||||
isMyGift: message.isOutgoing || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getPrizeStarsTransactionFromGiveaway(message: ApiMessage): ApiStarsTransaction | undefined {
|
||||
@ -373,7 +423,8 @@ export function getPrizeStarsTransactionFromGiveaway(message: ApiMessage): ApiSt
|
||||
|
||||
return {
|
||||
id: transactionId,
|
||||
stars: {
|
||||
amount: {
|
||||
currency: STARS_CURRENCY_CODE,
|
||||
amount: stars,
|
||||
nanos: 0,
|
||||
},
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type {
|
||||
ApiReceiptRegular,
|
||||
ApiReceiptStars,
|
||||
ApiStarsAmount,
|
||||
ApiStarsSubscription,
|
||||
ApiStarsTransaction,
|
||||
ApiTypeCurrencyAmount,
|
||||
} from '../../api/types';
|
||||
import type {
|
||||
PaymentStep,
|
||||
@ -15,6 +15,7 @@ import type {
|
||||
GlobalState, TabArgs, TabState,
|
||||
} from '../types';
|
||||
|
||||
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../config';
|
||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||
import { selectStarsPayment, selectTabState } from '../selectors';
|
||||
import { updateTabState } from './tabs';
|
||||
@ -137,15 +138,29 @@ export function closeInvoice<T extends GlobalState>(
|
||||
}
|
||||
|
||||
export function updateStarsBalance<T extends GlobalState>(
|
||||
global: T, balance: ApiStarsAmount,
|
||||
global: T, balance: ApiTypeCurrencyAmount,
|
||||
): T {
|
||||
return {
|
||||
...global,
|
||||
stars: {
|
||||
...global.stars,
|
||||
balance,
|
||||
},
|
||||
};
|
||||
if (balance.currency === STARS_CURRENCY_CODE) {
|
||||
return {
|
||||
...global,
|
||||
stars: {
|
||||
...global.stars,
|
||||
balance,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (balance.currency === TON_CURRENCY_CODE) {
|
||||
return {
|
||||
...global,
|
||||
ton: {
|
||||
...global.ton,
|
||||
balance,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return global;
|
||||
}
|
||||
|
||||
export function appendStarsTransactions<T extends GlobalState>(
|
||||
@ -153,7 +168,31 @@ export function appendStarsTransactions<T extends GlobalState>(
|
||||
type: StarsTransactionType,
|
||||
transactions: ApiStarsTransaction[],
|
||||
nextOffset?: string,
|
||||
isTon?: boolean,
|
||||
): T {
|
||||
if (isTon) {
|
||||
const history = global.ton?.history;
|
||||
if (!history) {
|
||||
return global;
|
||||
}
|
||||
|
||||
const newTypeObject = {
|
||||
transactions: (history[type]?.transactions || []).concat(transactions),
|
||||
nextOffset,
|
||||
};
|
||||
|
||||
return {
|
||||
...global,
|
||||
ton: {
|
||||
...global.ton,
|
||||
history: {
|
||||
...history,
|
||||
[type]: newTypeObject,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const history = global.stars?.history;
|
||||
if (!history) {
|
||||
return global;
|
||||
@ -238,7 +277,8 @@ export function openStarsTransactionFromReceipt<T extends GlobalState>(
|
||||
type: 'peer',
|
||||
id: receipt.botId,
|
||||
},
|
||||
stars: {
|
||||
amount: {
|
||||
currency: STARS_CURRENCY_CODE,
|
||||
amount: receipt.totalAmount,
|
||||
nanos: 0,
|
||||
},
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import type { ApiSticker, ApiStickerSet, ApiStickerSetInfo } from '../../api/types';
|
||||
import type { GlobalState, TabArgs } from '../types';
|
||||
|
||||
import { RESTRICTED_EMOJI_SET_ID } from '../../config';
|
||||
import { RESTRICTED_EMOJI_SET_ID, TON_CURRENCY_CODE } from '../../config';
|
||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||
import { convertCurrencyFromBaseUnit } from '../../util/formatCurrency';
|
||||
import { selectTabState } from './tabs';
|
||||
import { selectIsCurrentUserPremium } from './users';
|
||||
|
||||
@ -21,6 +22,12 @@ const STAR_EMOTICON: Record<number, string> = {
|
||||
5000: `${4}\u{FE0F}\u20E3`,
|
||||
};
|
||||
|
||||
const TON_EMOTICON: Record<number, string> = {
|
||||
1: `${1}\u{FE0F}\u20E3`,
|
||||
10: `${2}\u{FE0F}\u20E3`,
|
||||
50: `${3}\u{FE0F}\u20E3`,
|
||||
};
|
||||
|
||||
export function selectIsStickerFavorite<T extends GlobalState>(global: T, sticker: ApiSticker) {
|
||||
const { stickers } = global.stickers.favorite;
|
||||
return stickers && stickers.some(({ id }) => id === sticker.id);
|
||||
@ -194,3 +201,20 @@ export function selectGiftStickerForStars<T extends GlobalState>(global: T, star
|
||||
|
||||
return stickers.find((sticker) => sticker.emoji === emoji) || stickers[0];
|
||||
}
|
||||
|
||||
export function selectGiftStickerForTon<T extends GlobalState>(global: T, amount?: number) {
|
||||
const stickers = global.tonGifts?.stickers;
|
||||
if (!stickers || !amount) return undefined;
|
||||
const convertedAmount = convertCurrencyFromBaseUnit(amount, TON_CURRENCY_CODE);
|
||||
|
||||
let emoji;
|
||||
if (convertedAmount < 10) {
|
||||
emoji = TON_EMOTICON[1];
|
||||
} else if (convertedAmount < 50) {
|
||||
emoji = TON_EMOTICON[10];
|
||||
} else {
|
||||
emoji = TON_EMOTICON[50];
|
||||
}
|
||||
|
||||
return stickers.find((sticker) => sticker.emoji === emoji) || stickers[0];
|
||||
}
|
||||
|
||||
@ -49,6 +49,7 @@ import type {
|
||||
ApiStickerSetInfo,
|
||||
ApiThemeParameters,
|
||||
ApiTodoItem,
|
||||
ApiTypeCurrencyAmount,
|
||||
ApiTypePrepaidGiveaway,
|
||||
ApiUpdate,
|
||||
ApiUser,
|
||||
@ -1278,6 +1279,7 @@ export interface ActionPayloads {
|
||||
loadStarStatus: undefined;
|
||||
loadStarsTransactions: {
|
||||
type: StarsTransactionType;
|
||||
isTon?: boolean;
|
||||
};
|
||||
loadStarsSubscriptions: undefined;
|
||||
changeStarsSubscription: {
|
||||
@ -1302,6 +1304,7 @@ export interface ActionPayloads {
|
||||
purpose?: string;
|
||||
};
|
||||
shouldIgnoreBalance?: boolean;
|
||||
currency?: ApiTypeCurrencyAmount['currency'];
|
||||
} & WithTabId;
|
||||
closeStarsBalanceModal: WithTabId | undefined;
|
||||
|
||||
@ -2483,6 +2486,7 @@ export interface ActionPayloads {
|
||||
};
|
||||
|
||||
loadPremiumGifts: undefined;
|
||||
loadTonGifts: undefined;
|
||||
loadStarGifts: undefined;
|
||||
updateResaleGiftsFilter: {
|
||||
filter: ResaleGiftsFilterOptions;
|
||||
|
||||
@ -37,6 +37,7 @@ import type {
|
||||
ApiSticker,
|
||||
ApiStickerSet,
|
||||
ApiTimezone,
|
||||
ApiTonAmount,
|
||||
ApiTranscription,
|
||||
ApiUpdateAuthorizationStateType,
|
||||
ApiUpdateConnectionStateType,
|
||||
@ -367,6 +368,7 @@ export type GlobalState = {
|
||||
defaultTopicIconsId?: string;
|
||||
defaultStatusIconsId?: string;
|
||||
premiumGifts?: ApiStickerSet;
|
||||
tonGifts?: ApiStickerSet;
|
||||
emojiKeywords: Record<string, EmojiKeywords | undefined>;
|
||||
|
||||
collectibleEmojiStatuses?: {
|
||||
@ -452,6 +454,10 @@ export type GlobalState = {
|
||||
history: StarsTransactionHistory;
|
||||
subscriptions?: StarsSubscriptions;
|
||||
};
|
||||
ton?: {
|
||||
balance: ApiTonAmount;
|
||||
history: StarsTransactionHistory;
|
||||
};
|
||||
};
|
||||
|
||||
export type RequiredGlobalState = GlobalState & { _: never };
|
||||
|
||||
@ -45,6 +45,7 @@ import type {
|
||||
ApiStarsTransaction,
|
||||
ApiStarTopupOption,
|
||||
ApiSticker,
|
||||
ApiTypeCurrencyAmount,
|
||||
ApiTypePrepaidGiveaway,
|
||||
ApiTypeStoryView,
|
||||
ApiUser,
|
||||
@ -795,6 +796,7 @@ export type TabState = {
|
||||
balanceNeeded: number;
|
||||
purpose?: string;
|
||||
};
|
||||
currency?: ApiTypeCurrencyAmount['currency'];
|
||||
};
|
||||
|
||||
giftInfoModal?: {
|
||||
|
||||
@ -318,10 +318,12 @@ body:not(.is-ios) {
|
||||
}
|
||||
|
||||
// Increase specificity to override the default icon style
|
||||
.ton-amount-icon.ton-amount-icon,
|
||||
.star-amount-icon.star-amount-icon {
|
||||
margin-inline-start: 0.375em; // Prevent sticking to the text without using `white-space: pre`
|
||||
margin-inline-end: 0.2em; // Prevent sticking to the text without using `white-space: pre`
|
||||
line-height: inherit; // Vertical centring
|
||||
vertical-align: text-bottom; // As regular text
|
||||
}
|
||||
|
||||
.shared-canvas-container {
|
||||
|
||||
25
src/types/language.d.ts
vendored
25
src/types/language.d.ts
vendored
@ -576,6 +576,7 @@ export interface LangPair {
|
||||
'MenuStickers': undefined;
|
||||
'MenuAnimations': undefined;
|
||||
'MenuStars': undefined;
|
||||
'MenuTon': undefined;
|
||||
'MenuSendGift': undefined;
|
||||
'MenuTelegramFaq': undefined;
|
||||
'MenuPrivacyPolicy': undefined;
|
||||
@ -1549,9 +1550,13 @@ export interface LangPair {
|
||||
'TitleTime': undefined;
|
||||
'TitleSuggestMessage': undefined;
|
||||
'TitleSuggestedChanges': undefined;
|
||||
'SuggestMessageNoPrice': undefined;
|
||||
'EnterPriceInStars': undefined;
|
||||
'EnterPriceInTon': undefined;
|
||||
'SuggestMessagePriceDescriptionStars': undefined;
|
||||
'SuggestMessagePriceDescriptionTon': undefined;
|
||||
'SuggestMessageDateTimeHint': undefined;
|
||||
'TitleAnytime': undefined;
|
||||
'SuggestMessageAnytime': undefined;
|
||||
'ButtonOfferFree': undefined;
|
||||
'ButtonUpdateTerms': undefined;
|
||||
'InputPlaceholderPrice': undefined;
|
||||
@ -1563,6 +1568,7 @@ export interface LangPair {
|
||||
'SuggestedPostRejectedNotification': undefined;
|
||||
'SuggestedPostAgreementReached': undefined;
|
||||
'CurrencyStars': undefined;
|
||||
'CurrencyTon': undefined;
|
||||
'DeclineReasonPlaceholder': undefined;
|
||||
'SuggestedPostRejectedYou': undefined;
|
||||
'SuggestedPostRejectedWithReasonYou': undefined;
|
||||
@ -1590,6 +1596,10 @@ export interface LangPair {
|
||||
'ToDoListErrorChooseTitle': undefined;
|
||||
'ToDoListErrorChooseTasks': undefined;
|
||||
'PremiumPreviewTodo': undefined;
|
||||
'DescriptionAboutTon': undefined;
|
||||
'ButtonTopUpViaFragment': undefined;
|
||||
'TonModalHint': undefined;
|
||||
'TonGiftReceived': undefined;
|
||||
}
|
||||
|
||||
export interface LangPairWithVariables<V = LangVariable> {
|
||||
@ -2425,6 +2435,13 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'from': V;
|
||||
'amount': V;
|
||||
};
|
||||
'TonAmount': {
|
||||
'amount': V;
|
||||
};
|
||||
'ActionGiftCostCrypto': {
|
||||
'cryptoPrice': V;
|
||||
'price': V;
|
||||
};
|
||||
'ActionPaymentRefunded': {
|
||||
'peer': V;
|
||||
'amount': V;
|
||||
@ -2584,9 +2601,6 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'user': V;
|
||||
'changes': V;
|
||||
};
|
||||
'SuggestMessagePriceDescription': {
|
||||
'currency': V;
|
||||
};
|
||||
'SuggestMessageTimeDescription': {
|
||||
'hint': V;
|
||||
'duration': V;
|
||||
@ -3035,6 +3049,9 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
|
||||
'ActionGiftStarsTitle': {
|
||||
'amount': V;
|
||||
};
|
||||
'TonAmountText': {
|
||||
'amount': V;
|
||||
};
|
||||
'ActionBoostApplyYou': {
|
||||
'count': V;
|
||||
};
|
||||
|
||||
@ -2,8 +2,24 @@ import { type TeactNode } from '../lib/teact/teact';
|
||||
|
||||
import type { LangFn } from './localization';
|
||||
|
||||
import { STARS_CURRENCY_CODE } from '../config';
|
||||
import { formatStarsAsIcon } from './localization/format';
|
||||
import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../config';
|
||||
import { formatStarsAsIcon, formatTonAsIcon } from './localization/format';
|
||||
|
||||
export function convertCurrencyFromBaseUnit(amount: number, currency: string) {
|
||||
return amount / 10 ** getCurrencyExp(currency);
|
||||
}
|
||||
|
||||
export function convertCurrencyToBaseUnit(amount: number, currency: string) {
|
||||
return amount * 10 ** getCurrencyExp(currency);
|
||||
}
|
||||
|
||||
export function convertTonFromNanos(nanos: number): number {
|
||||
return convertCurrencyFromBaseUnit(nanos, TON_CURRENCY_CODE);
|
||||
}
|
||||
|
||||
export function convertTonToNanos(ton: number): number {
|
||||
return convertCurrencyToBaseUnit(ton, TON_CURRENCY_CODE);
|
||||
}
|
||||
|
||||
export function formatCurrency(
|
||||
lang: LangFn,
|
||||
@ -15,15 +31,24 @@ export function formatCurrency(
|
||||
asFontIcon?: boolean;
|
||||
},
|
||||
): TeactNode {
|
||||
const price = totalPrice / 10 ** getCurrencyExp(currency);
|
||||
const price = convertCurrencyFromBaseUnit(totalPrice, currency);
|
||||
|
||||
if (currency === STARS_CURRENCY_CODE) {
|
||||
return formatStarsAsIcon(lang, price, { asFont: options?.asFontIcon, className: options?.iconClassName });
|
||||
}
|
||||
|
||||
if (currency === TON_CURRENCY_CODE) {
|
||||
return formatTonAsIcon(lang, price, { asFont: options?.asFontIcon, className: options?.iconClassName });
|
||||
}
|
||||
|
||||
return formatCurrencyAsString(totalPrice, currency, lang.code, options);
|
||||
}
|
||||
|
||||
export function convertTonToUsd(amount: number, usdRate: number): number {
|
||||
const tonInRegularUnits = convertTonFromNanos(amount);
|
||||
return tonInRegularUnits * usdRate * 100;
|
||||
}
|
||||
|
||||
export function formatCurrencyAsString(
|
||||
totalPrice: number,
|
||||
currency: string,
|
||||
@ -32,7 +57,7 @@ export function formatCurrencyAsString(
|
||||
shouldOmitFractions?: boolean;
|
||||
},
|
||||
) {
|
||||
const price = totalPrice / 10 ** getCurrencyExp(currency);
|
||||
const price = convertCurrencyFromBaseUnit(totalPrice, currency);
|
||||
|
||||
if ((options?.shouldOmitFractions || currency === STARS_CURRENCY_CODE) && Number.isInteger(price)) {
|
||||
return new Intl.NumberFormat(locale, {
|
||||
@ -43,6 +68,15 @@ export function formatCurrencyAsString(
|
||||
}).format(price);
|
||||
}
|
||||
|
||||
if (currency === TON_CURRENCY_CODE) {
|
||||
return new Intl.NumberFormat(locale, {
|
||||
style: 'currency',
|
||||
currency,
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 10,
|
||||
}).format(price);
|
||||
}
|
||||
|
||||
return new Intl.NumberFormat(locale, {
|
||||
style: 'currency',
|
||||
currency,
|
||||
@ -50,7 +84,7 @@ export function formatCurrencyAsString(
|
||||
}
|
||||
|
||||
function getCurrencyExp(currency: string) {
|
||||
if (currency === 'TON') {
|
||||
if (currency === TON_CURRENCY_CODE) {
|
||||
return 9;
|
||||
}
|
||||
if (currency === 'CLF') {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { LangFn } from './types';
|
||||
|
||||
import { STARS_ICON_PLACEHOLDER } from '../../config';
|
||||
import { convertCurrencyFromBaseUnit } from '../../util/formatCurrency';
|
||||
import buildClassName from '../buildClassName';
|
||||
|
||||
import Icon from '../../components/common/icons/Icon';
|
||||
@ -10,6 +11,37 @@ export function formatStarsAsText(lang: LangFn, amount: number) {
|
||||
return lang('StarsAmountText', { amount }, { pluralValue: amount });
|
||||
}
|
||||
|
||||
export function formatTonAsText(lang: LangFn, amount: number) {
|
||||
return lang('TonAmountText', { amount: lang.preciseNumber(amount) }, { pluralValue: amount });
|
||||
}
|
||||
|
||||
export function formatTonAsIcon(lang: LangFn, amount: number | string, options?: {
|
||||
asFont?: boolean; className?: string; containerClassName?: string; shouldConvertFromNanos?: boolean; }) {
|
||||
const { className, containerClassName, shouldConvertFromNanos } = options || {};
|
||||
const formattedAmount = shouldConvertFromNanos ? convertCurrencyFromBaseUnit(Number(amount), 'TON') : amount;
|
||||
const icon = <Icon name="toncoin" className={buildClassName('ton-amount-icon', className)} />;
|
||||
|
||||
if (containerClassName) {
|
||||
return (
|
||||
<span className={containerClassName}>
|
||||
{lang('TonAmount', { amount: formattedAmount }, {
|
||||
withNodes: true,
|
||||
specialReplacement: {
|
||||
'💎': icon,
|
||||
},
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return lang('TonAmount', { amount: formattedAmount }, {
|
||||
withNodes: true,
|
||||
specialReplacement: {
|
||||
'💎': icon,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function formatStarsAsIcon(lang: LangFn, amount: number | string, options?: {
|
||||
asFont?: boolean; className?: string; containerClassName?: string; }) {
|
||||
const { asFont, className, containerClassName } = options || {};
|
||||
|
||||
@ -169,6 +169,10 @@ function createFormatters() {
|
||||
conjunction: createListFormat(langCode, 'conjunction'),
|
||||
disjunction: createListFormat(langCode, 'disjunction'),
|
||||
number: new Intl.NumberFormat(langCode),
|
||||
preciseNumber: new Intl.NumberFormat(langCode, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 10,
|
||||
}),
|
||||
};
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
@ -179,6 +183,10 @@ function createFormatters() {
|
||||
conjunction: createListFormat(FORMATTERS_FALLBACK_LANG, 'conjunction'),
|
||||
disjunction: createListFormat(FORMATTERS_FALLBACK_LANG, 'disjunction'),
|
||||
number: new Intl.NumberFormat(FORMATTERS_FALLBACK_LANG),
|
||||
preciseNumber: new Intl.NumberFormat(FORMATTERS_FALLBACK_LANG, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 10,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -325,6 +333,7 @@ function createTranslationFn(): LangFn {
|
||||
fn.conjunction = (list: string[]) => formatters?.conjunction.format(list) || list.join(', ');
|
||||
fn.disjunction = (list: string[]) => formatters?.disjunction.format(list) || list.join(', ');
|
||||
fn.number = (value: number) => formatters?.number.format(value) || String(value);
|
||||
fn.preciseNumber = (value: number) => formatters?.preciseNumber.format(value) || String(value);
|
||||
fn.internalFormatters = formatters!;
|
||||
fn.languageInfo = language!;
|
||||
return fn;
|
||||
|
||||
@ -155,6 +155,7 @@ export type LangFn = {
|
||||
conjunction: (list: string[]) => string;
|
||||
disjunction: (list: string[]) => string;
|
||||
number: (value: number) => string;
|
||||
preciseNumber: (value: number) => string;
|
||||
internalFormatters: LangFormatters;
|
||||
isRtl?: boolean;
|
||||
rawCode: string;
|
||||
@ -171,6 +172,7 @@ export type LangFormatters = {
|
||||
conjunction: ListFormat;
|
||||
disjunction: ListFormat;
|
||||
number: Intl.NumberFormat;
|
||||
preciseNumber: Intl.NumberFormat;
|
||||
};
|
||||
|
||||
/* GUARDS */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user