Gifts: Support purchase offer (#6579)
This commit is contained in:
parent
bb4678dd5a
commit
049db12d92
@ -85,6 +85,7 @@ export interface GramJsAppConfig extends LimitsConfig {
|
|||||||
ton_blockchain_explorer_url?: string;
|
ton_blockchain_explorer_url?: string;
|
||||||
stars_paid_messages_available?: boolean;
|
stars_paid_messages_available?: boolean;
|
||||||
stars_usd_withdraw_rate_x1000?: number;
|
stars_usd_withdraw_rate_x1000?: number;
|
||||||
|
stars_usd_sell_rate_x1000?: number;
|
||||||
stars_paid_message_commission_permille?: number;
|
stars_paid_message_commission_permille?: number;
|
||||||
stars_paid_message_amount_max?: number;
|
stars_paid_message_amount_max?: number;
|
||||||
stargifts_pinned_to_top_limit?: number;
|
stargifts_pinned_to_top_limit?: number;
|
||||||
@ -203,6 +204,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
|||||||
starsPaidMessageCommissionPermille: appConfig.stars_paid_message_commission_permille,
|
starsPaidMessageCommissionPermille: appConfig.stars_paid_message_commission_permille,
|
||||||
starsPaidMessageAmountMax: appConfig.stars_paid_message_amount_max,
|
starsPaidMessageAmountMax: appConfig.stars_paid_message_amount_max,
|
||||||
starsUsdWithdrawRateX1000: appConfig.stars_usd_withdraw_rate_x1000,
|
starsUsdWithdrawRateX1000: appConfig.stars_usd_withdraw_rate_x1000,
|
||||||
|
starsUsdSellRateX1000: appConfig.stars_usd_sell_rate_x1000,
|
||||||
bandwidthPremiumNotifyPeriod: appConfig.upload_premium_speedup_notify_period,
|
bandwidthPremiumNotifyPeriod: appConfig.upload_premium_speedup_notify_period,
|
||||||
bandwidthPremiumUploadSpeedup: appConfig.upload_premium_speedup_upload,
|
bandwidthPremiumUploadSpeedup: appConfig.upload_premium_speedup_upload,
|
||||||
bandwidthPremiumDownloadSpeedup: appConfig.upload_premium_speedup_download,
|
bandwidthPremiumDownloadSpeedup: appConfig.upload_premium_speedup_download,
|
||||||
|
|||||||
@ -35,6 +35,7 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
|||||||
const {
|
const {
|
||||||
id, num, ownerId, ownerName, title, attributes, availabilityIssued, availabilityTotal, slug, ownerAddress,
|
id, num, ownerId, ownerName, title, attributes, availabilityIssued, availabilityTotal, slug, ownerAddress,
|
||||||
giftAddress, resellAmount, releasedBy, resaleTonOnly, requirePremium, valueCurrency, valueAmount, giftId,
|
giftAddress, resellAmount, releasedBy, resaleTonOnly, requirePremium, valueCurrency, valueAmount, giftId,
|
||||||
|
valueUsdAmount,
|
||||||
} = starGift;
|
} = starGift;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -56,7 +57,9 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
|||||||
resaleTonOnly,
|
resaleTonOnly,
|
||||||
valueCurrency,
|
valueCurrency,
|
||||||
valueAmount: toJSNumber(valueAmount),
|
valueAmount: toJSNumber(valueAmount),
|
||||||
|
valueUsdAmount: toJSNumber(valueUsdAmount),
|
||||||
regularGiftId: giftId.toString(),
|
regularGiftId: giftId.toString(),
|
||||||
|
offerMinStars: starGift.offerMinStars,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -426,7 +426,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
|||||||
if (action instanceof GramJs.MessageActionStarGiftUnique) {
|
if (action instanceof GramJs.MessageActionStarGiftUnique) {
|
||||||
const {
|
const {
|
||||||
upgrade, transferred, saved, refunded, gift, canExportAt, transferStars, fromId, peer, savedId,
|
upgrade, transferred, saved, refunded, gift, canExportAt, transferStars, fromId, peer, savedId,
|
||||||
resaleAmount, prepaidUpgrade, dropOriginalDetailsStars,
|
resaleAmount, prepaidUpgrade, dropOriginalDetailsStars, fromOffer,
|
||||||
} = action;
|
} = action;
|
||||||
|
|
||||||
const starGift = buildApiStarGift(gift);
|
const starGift = buildApiStarGift(gift);
|
||||||
@ -440,6 +440,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
|||||||
isSaved: saved,
|
isSaved: saved,
|
||||||
isRefunded: refunded,
|
isRefunded: refunded,
|
||||||
isPrepaidUpgrade: prepaidUpgrade,
|
isPrepaidUpgrade: prepaidUpgrade,
|
||||||
|
isFromOffer: fromOffer,
|
||||||
gift: starGift,
|
gift: starGift,
|
||||||
canExportAt,
|
canExportAt,
|
||||||
transferStars: toJSNumber(transferStars),
|
transferStars: toJSNumber(transferStars),
|
||||||
@ -523,6 +524,38 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
|||||||
items: list.map(buildTodoItem),
|
items: list.map(buildTodoItem),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (action instanceof GramJs.MessageActionStarGiftPurchaseOffer) {
|
||||||
|
const {
|
||||||
|
accepted, declined, gift, price, expiresAt,
|
||||||
|
} = action;
|
||||||
|
|
||||||
|
const starGift = buildApiStarGift(gift);
|
||||||
|
if (starGift.type !== 'starGiftUnique') return UNSUPPORTED_ACTION;
|
||||||
|
|
||||||
|
return {
|
||||||
|
mediaType: 'action',
|
||||||
|
type: 'starGiftPurchaseOffer',
|
||||||
|
isAccepted: accepted,
|
||||||
|
isDeclined: declined,
|
||||||
|
gift: starGift,
|
||||||
|
price: buildApiCurrencyAmount(price),
|
||||||
|
expiresAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action instanceof GramJs.MessageActionStarGiftPurchaseOfferDeclined) {
|
||||||
|
const { expired, gift, price } = action;
|
||||||
|
|
||||||
|
const starGift = buildApiStarGift(gift);
|
||||||
|
if (starGift.type !== 'starGiftUnique') return UNSUPPORTED_ACTION;
|
||||||
|
|
||||||
|
return {
|
||||||
|
mediaType: 'action',
|
||||||
|
type: 'starGiftPurchaseOfferDeclined',
|
||||||
|
isExpired: expired,
|
||||||
|
gift: starGift,
|
||||||
|
price: buildApiCurrencyAmount(price),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return UNSUPPORTED_ACTION;
|
return UNSUPPORTED_ACTION;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -590,3 +590,18 @@ export async function fetchStarGiftCollections({
|
|||||||
collections: result.collections.map(buildApiStarGiftCollection).filter(Boolean),
|
collections: result.collections.map(buildApiStarGiftCollection).filter(Boolean),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveStarGiftOffer({
|
||||||
|
offerMsgId,
|
||||||
|
shouldDecline,
|
||||||
|
}: {
|
||||||
|
offerMsgId: number;
|
||||||
|
shouldDecline?: boolean;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.payments.ResolveStarGiftOffer({
|
||||||
|
offerMsgId,
|
||||||
|
decline: shouldDecline || undefined,
|
||||||
|
}), {
|
||||||
|
shouldReturnTrue: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -265,6 +265,7 @@ export interface ApiMessageActionStarGiftUnique extends ActionMediaType {
|
|||||||
isSaved?: true;
|
isSaved?: true;
|
||||||
isRefunded?: true;
|
isRefunded?: true;
|
||||||
isPrepaidUpgrade?: true;
|
isPrepaidUpgrade?: true;
|
||||||
|
isFromOffer?: true;
|
||||||
gift: ApiStarGiftUnique;
|
gift: ApiStarGiftUnique;
|
||||||
canExportAt?: number;
|
canExportAt?: number;
|
||||||
transferStars?: number;
|
transferStars?: number;
|
||||||
@ -329,6 +330,22 @@ export interface ApiMessageActionTodoAppendTasks extends ActionMediaType {
|
|||||||
items: ApiTodoItem[];
|
items: ApiTodoItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiMessageActionStarGiftPurchaseOffer extends ActionMediaType {
|
||||||
|
type: 'starGiftPurchaseOffer';
|
||||||
|
isAccepted?: true;
|
||||||
|
isDeclined?: true;
|
||||||
|
gift: ApiStarGiftUnique;
|
||||||
|
price: ApiTypeCurrencyAmount;
|
||||||
|
expiresAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiMessageActionStarGiftPurchaseOfferDeclined extends ActionMediaType {
|
||||||
|
type: 'starGiftPurchaseOfferDeclined';
|
||||||
|
isExpired?: true;
|
||||||
|
gift: ApiStarGiftUnique;
|
||||||
|
price: ApiTypeCurrencyAmount;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ApiMessageActionUnsupported extends ActionMediaType {
|
export interface ApiMessageActionUnsupported extends ActionMediaType {
|
||||||
type: 'unsupported';
|
type: 'unsupported';
|
||||||
}
|
}
|
||||||
@ -348,4 +365,5 @@ export type ApiMessageAction = ApiMessageActionUnsupported | ApiMessageActionCha
|
|||||||
| ApiMessageActionGiftTon | ApiMessageActionPrizeStars | ApiMessageActionStarGift | ApiMessageActionStarGiftUnique
|
| ApiMessageActionGiftTon | ApiMessageActionPrizeStars | ApiMessageActionStarGift | ApiMessageActionStarGiftUnique
|
||||||
| ApiMessageActionPaidMessagesRefunded | ApiMessageActionPaidMessagesPrice | ApiMessageActionSuggestedPostApproval
|
| ApiMessageActionPaidMessagesRefunded | ApiMessageActionPaidMessagesPrice | ApiMessageActionSuggestedPostApproval
|
||||||
| ApiMessageActionSuggestedPostSuccess | ApiMessageActionSuggestedPostRefund | ApiMessageActionTodoCompletions
|
| ApiMessageActionSuggestedPostSuccess | ApiMessageActionSuggestedPostRefund | ApiMessageActionTodoCompletions
|
||||||
| ApiMessageActionTodoAppendTasks;
|
| ApiMessageActionTodoAppendTasks | ApiMessageActionStarGiftPurchaseOffer
|
||||||
|
| ApiMessageActionStarGiftPurchaseOfferDeclined;
|
||||||
|
|||||||
@ -943,6 +943,12 @@ export interface KeyboardButtonOpenThread {
|
|||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface KeyboardButtonGiftOffer {
|
||||||
|
type: 'giftOffer';
|
||||||
|
text: string;
|
||||||
|
buttonType: 'accept' | 'reject';
|
||||||
|
}
|
||||||
|
|
||||||
export type ApiKeyboardButton = (
|
export type ApiKeyboardButton = (
|
||||||
ApiKeyboardButtonSimple
|
ApiKeyboardButtonSimple
|
||||||
| ApiKeyboardButtonReceipt
|
| ApiKeyboardButtonReceipt
|
||||||
@ -957,6 +963,7 @@ export type ApiKeyboardButton = (
|
|||||||
| ApiKeyboardButtonCopy
|
| ApiKeyboardButtonCopy
|
||||||
| KeyboardButtonSuggestedMessage
|
| KeyboardButtonSuggestedMessage
|
||||||
| KeyboardButtonOpenThread
|
| KeyboardButtonOpenThread
|
||||||
|
| KeyboardButtonGiftOffer
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ApiKeyboardButtons = ApiKeyboardButton[][];
|
export type ApiKeyboardButtons = ApiKeyboardButton[][];
|
||||||
|
|||||||
@ -240,6 +240,7 @@ export interface ApiAppConfig {
|
|||||||
starsPaidMessageCommissionPermille?: number;
|
starsPaidMessageCommissionPermille?: number;
|
||||||
starsPaidMessageAmountMax?: number;
|
starsPaidMessageAmountMax?: number;
|
||||||
starsUsdWithdrawRateX1000: number;
|
starsUsdWithdrawRateX1000: number;
|
||||||
|
starsUsdSellRateX1000?: number;
|
||||||
bandwidthPremiumNotifyPeriod?: number;
|
bandwidthPremiumNotifyPeriod?: number;
|
||||||
bandwidthPremiumUploadSpeedup?: number;
|
bandwidthPremiumUploadSpeedup?: number;
|
||||||
bandwidthPremiumDownloadSpeedup?: number;
|
bandwidthPremiumDownloadSpeedup?: number;
|
||||||
|
|||||||
@ -59,6 +59,8 @@ export interface ApiStarGiftUnique {
|
|||||||
resaleTonOnly?: true;
|
resaleTonOnly?: true;
|
||||||
valueCurrency?: string;
|
valueCurrency?: string;
|
||||||
valueAmount?: number;
|
valueAmount?: number;
|
||||||
|
valueUsdAmount?: number;
|
||||||
|
offerMinStars?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiStarGift = ApiStarGiftRegular | ApiStarGiftUnique;
|
export type ApiStarGift = ApiStarGiftRegular | ApiStarGiftUnique;
|
||||||
|
|||||||
@ -1526,6 +1526,7 @@
|
|||||||
"GiftInfoFrom" = "From";
|
"GiftInfoFrom" = "From";
|
||||||
"GiftInfoDate" = "Date";
|
"GiftInfoDate" = "Date";
|
||||||
"GiftInfoValue" = "Value";
|
"GiftInfoValue" = "Value";
|
||||||
|
"GiftInfoValueAmount" = "~ {amount}";
|
||||||
"GiftInfoConvert_one" = "Convert to {amount} Star";
|
"GiftInfoConvert_one" = "Convert to {amount} Star";
|
||||||
"GiftInfoConvert_other" = "Convert to {amount} Stars";
|
"GiftInfoConvert_other" = "Convert to {amount} Stars";
|
||||||
"GiftInfoConvertTitle" = "Convert Gift to Stars";
|
"GiftInfoConvertTitle" = "Convert Gift to Stars";
|
||||||
@ -1912,6 +1913,8 @@
|
|||||||
"ActionStarGiftTransferredSelf" = "You transferred a unique collectible";
|
"ActionStarGiftTransferredSelf" = "You transferred a unique collectible";
|
||||||
"ActionStarGiftTransferredUnknown" = "Someone transferred you a gift";
|
"ActionStarGiftTransferredUnknown" = "Someone transferred you a gift";
|
||||||
"ActionStarGiftTransferredUnknownChannel" = "Someone transferred a gift to {channel}";
|
"ActionStarGiftTransferredUnknownChannel" = "Someone transferred a gift to {channel}";
|
||||||
|
"ActionStarGiftSoldFromOffer" = "You sold {gift} to {user} for {cost}";
|
||||||
|
"ActionStarGiftBoughtFromOffer" = "{user} accepted your offer and sold you {gift} for {cost}";
|
||||||
"ActionStarGiftReceivedAnonymous" = "Someone sent you a gift for {cost}";
|
"ActionStarGiftReceivedAnonymous" = "Someone sent you a gift for {cost}";
|
||||||
"ActionStarGiftSentChannel" = "{user} sent a gift to {channel} for {cost}";
|
"ActionStarGiftSentChannel" = "{user} sent a gift to {channel} for {cost}";
|
||||||
"ActionStarGiftSentChannelYou" = "You sent a gift to {channel} for {cost}";
|
"ActionStarGiftSentChannelYou" = "You sent a gift to {channel} for {cost}";
|
||||||
@ -1942,6 +1945,24 @@
|
|||||||
"ActionStarGiftAuctionWon" = "You won the auction with a bid of {cost}";
|
"ActionStarGiftAuctionWon" = "You won the auction with a bid of {cost}";
|
||||||
"ActionStarGiftAuctionFor" = "Gift for {peer}";
|
"ActionStarGiftAuctionFor" = "Gift for {peer}";
|
||||||
"ActionStarGiftAuctionBought" = "You bought this gift at auction for {cost}.";
|
"ActionStarGiftAuctionBought" = "You bought this gift at auction for {cost}.";
|
||||||
|
"ActionStarGiftOfferOutgoing" = "You offered {peer} {cost} for {gift}.";
|
||||||
|
"ActionStarGiftOfferIncoming" = "{peer} offered you {cost} for {gift}.";
|
||||||
|
"ActionStarGiftOfferExpires" = "This offer expires in {time}";
|
||||||
|
"ActionStarGiftOfferAccepted" = "This offer was accepted.";
|
||||||
|
"ActionStarGiftOfferRejected" = "This offer was rejected.";
|
||||||
|
"ActionStarGiftOfferHasExpired" = "This offer has expired";
|
||||||
|
"ActionStarGiftOfferDeclinedOutgoing" = "You rejected {peer}'s offer to buy your {gift} for {cost}.";
|
||||||
|
"ActionStarGiftOfferDeclinedIncoming" = "{peer} rejected your offer to buy {gift} for {cost}.";
|
||||||
|
"GiftOfferReject" = "Reject";
|
||||||
|
"GiftOfferAccept" = "Accept";
|
||||||
|
"GiftOfferRejectTitle" = "Reject Offer";
|
||||||
|
"GiftOfferRejectText" = "Are you sure you want to reject the offer from **{user}**?";
|
||||||
|
"GiftOfferAcceptTitle" = "Sell Gift";
|
||||||
|
"GiftOfferAcceptText" = "Do you want to sell **{gift}** to **{user}** for **{price}**?";
|
||||||
|
"GiftOfferAcceptReceive" = "You will receive **{amount}** after fees.";
|
||||||
|
"GiftOfferAcceptButton" = "Sell for {amount}";
|
||||||
|
"GiftOfferPriceLow" = "The price you are offered is **{percent}** lower than the average price for {gift}";
|
||||||
|
"GiftOfferPriceHigh" = "The price you are offered is **{percent}** higher than the average price for {gift}";
|
||||||
"ActionSuggestedPhotoYou" = "You suggested this photo for {user}'s Telegram profile.";
|
"ActionSuggestedPhotoYou" = "You suggested this photo for {user}'s Telegram profile.";
|
||||||
"ActionSuggestedPhoto" = "{user} suggests this photo for your Telegram profile.";
|
"ActionSuggestedPhoto" = "{user} suggests this photo for your Telegram profile.";
|
||||||
"ActionSuggestedPhotoButton" = "View Photo";
|
"ActionSuggestedPhotoButton" = "View Photo";
|
||||||
|
|||||||
@ -26,5 +26,6 @@ export { default as GiftWithdrawModal } from '../components/modals/gift/withdraw
|
|||||||
export { default as GiftTransferModal } from '../components/modals/gift/transfer/GiftTransferModal';
|
export { default as GiftTransferModal } from '../components/modals/gift/transfer/GiftTransferModal';
|
||||||
export { default as GiftTransferConfirmModal } from '../components/modals/gift/transfer/GiftTransferConfirmModal';
|
export { default as GiftTransferConfirmModal } from '../components/modals/gift/transfer/GiftTransferConfirmModal';
|
||||||
export { default as GiftDescriptionRemoveModal } from '../components/modals/gift/message/GiftDescriptionRemoveModal';
|
export { default as GiftDescriptionRemoveModal } from '../components/modals/gift/message/GiftDescriptionRemoveModal';
|
||||||
|
export { default as GiftOfferAcceptModal } from '../components/modals/gift/offer/GiftOfferAcceptModal';
|
||||||
export { default as ChatRefundModal } from '../components/modals/stars/chatRefund/ChatRefundModal';
|
export { default as ChatRefundModal } from '../components/modals/stars/chatRefund/ChatRefundModal';
|
||||||
export { default as PriceConfirmModal } from '../components/modals/priceConfirm/PriceConfirmModal';
|
export { default as PriceConfirmModal } from '../components/modals/priceConfirm/PriceConfirmModal';
|
||||||
|
|||||||
@ -82,6 +82,16 @@
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contentWrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
:global(.InlineButtons) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.contextContainer {
|
.contextContainer {
|
||||||
grid-area: 1 / 1;
|
grid-area: 1 / 1;
|
||||||
}
|
}
|
||||||
@ -303,3 +313,7 @@
|
|||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.narrowWrapper {
|
||||||
|
max-width: 18rem;
|
||||||
|
}
|
||||||
|
|||||||
@ -10,11 +10,18 @@ import type {
|
|||||||
ThreadId,
|
ThreadId,
|
||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
import type { Signal } from '../../../util/signals';
|
import type { Signal } from '../../../util/signals';
|
||||||
import { type ApiMessage, type ApiPeer, MAIN_THREAD_ID } from '../../../api/types';
|
import {
|
||||||
|
type ApiKeyboardButton,
|
||||||
|
type ApiMessage,
|
||||||
|
type ApiPeer,
|
||||||
|
type KeyboardButtonGiftOffer,
|
||||||
|
MAIN_THREAD_ID,
|
||||||
|
} from '../../../api/types';
|
||||||
import { MediaViewerOrigin } from '../../../types';
|
import { MediaViewerOrigin } from '../../../types';
|
||||||
|
|
||||||
import { MESSAGE_APPEARANCE_DELAY } from '../../../config';
|
import { MESSAGE_APPEARANCE_DELAY } from '../../../config';
|
||||||
import { getMessageHtmlId } from '../../../global/helpers';
|
import { getMessageHtmlId } from '../../../global/helpers';
|
||||||
|
import { getPeerTitle } from '../../../global/helpers/peers';
|
||||||
import { getMessageReplyInfo } from '../../../global/helpers/replies';
|
import { getMessageReplyInfo } from '../../../global/helpers/replies';
|
||||||
import {
|
import {
|
||||||
selectActionMessageBg,
|
selectActionMessageBg,
|
||||||
@ -31,6 +38,7 @@ import { IS_TAURI } from '../../../util/browser/globalEnvironment';
|
|||||||
import { IS_ANDROID, IS_FLUID_BACKGROUND_SUPPORTED } from '../../../util/browser/windowEnvironment';
|
import { IS_ANDROID, IS_FLUID_BACKGROUND_SUPPORTED } from '../../../util/browser/windowEnvironment';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { isLocalMessageId } from '../../../util/keys/messageKey';
|
import { isLocalMessageId } from '../../../util/keys/messageKey';
|
||||||
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import { isElementInViewport } from '../../../util/visibility/isElementInViewport';
|
import { isElementInViewport } from '../../../util/visibility/isElementInViewport';
|
||||||
import { preventMessageInputBlur } from '../helpers/preventMessageInputBlur';
|
import { preventMessageInputBlur } from '../helpers/preventMessageInputBlur';
|
||||||
|
|
||||||
@ -39,23 +47,27 @@ import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
|||||||
import useEnsureMessage from '../../../hooks/useEnsureMessage';
|
import useEnsureMessage from '../../../hooks/useEnsureMessage';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import { type OnIntersectPinnedMessage } from '../hooks/usePinnedMessage';
|
import { type OnIntersectPinnedMessage } from '../hooks/usePinnedMessage';
|
||||||
import useFluidBackgroundFilter from './hooks/useFluidBackgroundFilter';
|
import useFluidBackgroundFilter from './hooks/useFluidBackgroundFilter';
|
||||||
import useFocusMessageListElement from './hooks/useFocusMessageListElement';
|
import useFocusMessageListElement from './hooks/useFocusMessageListElement';
|
||||||
|
|
||||||
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
import ActionMessageText from './ActionMessageText';
|
import ActionMessageText from './ActionMessageText';
|
||||||
import ChannelPhoto from './actions/ChannelPhoto';
|
import ChannelPhoto from './actions/ChannelPhoto';
|
||||||
import Gift from './actions/Gift';
|
import Gift from './actions/Gift';
|
||||||
import GiveawayPrize from './actions/GiveawayPrize';
|
import GiveawayPrize from './actions/GiveawayPrize';
|
||||||
import StarGift from './actions/StarGift';
|
import StarGift from './actions/StarGift';
|
||||||
|
import StarGiftPurchaseOffer from './actions/StarGiftPurchaseOffer';
|
||||||
import StarGiftUnique from './actions/StarGiftUnique';
|
import StarGiftUnique from './actions/StarGiftUnique';
|
||||||
import SuggestedPhoto from './actions/SuggestedPhoto';
|
import SuggestedPhoto from './actions/SuggestedPhoto';
|
||||||
import SuggestedPostApproval from './actions/SuggestedPostApproval';
|
import SuggestedPostApproval from './actions/SuggestedPostApproval';
|
||||||
import SuggestedPostBalanceTooLow from './actions/SuggestedPostBalanceTooLow';
|
import SuggestedPostBalanceTooLow from './actions/SuggestedPostBalanceTooLow';
|
||||||
import SuggestedPostRejected from './actions/SuggestedPostRejected';
|
import SuggestedPostRejected from './actions/SuggestedPostRejected';
|
||||||
import ContextMenuContainer from './ContextMenuContainer';
|
import ContextMenuContainer from './ContextMenuContainer';
|
||||||
|
import InlineButtons from './InlineButtons';
|
||||||
import Reactions from './reactions/Reactions';
|
import Reactions from './reactions/Reactions';
|
||||||
import SimilarChannels from './SimilarChannels';
|
import SimilarChannels from './SimilarChannels';
|
||||||
|
|
||||||
@ -101,7 +113,7 @@ const SINGLE_LINE_ACTIONS = new Set<ApiMessageAction['type']>([
|
|||||||
'unsupported',
|
'unsupported',
|
||||||
]);
|
]);
|
||||||
const HIDDEN_TEXT_ACTIONS = new Set<ApiMessageAction['type']>(['giftCode', 'prizeStars',
|
const HIDDEN_TEXT_ACTIONS = new Set<ApiMessageAction['type']>(['giftCode', 'prizeStars',
|
||||||
'suggestProfilePhoto', 'suggestedPostApproval']);
|
'suggestProfilePhoto', 'suggestedPostApproval', 'starGiftPurchaseOffer']);
|
||||||
|
|
||||||
const ActionMessage = ({
|
const ActionMessage = ({
|
||||||
message,
|
message,
|
||||||
@ -143,6 +155,8 @@ const ActionMessage = ({
|
|||||||
animateUnreadReaction,
|
animateUnreadReaction,
|
||||||
markMentionsRead,
|
markMentionsRead,
|
||||||
focusMessage,
|
focusMessage,
|
||||||
|
openGiftOfferAcceptModal,
|
||||||
|
declineStarGiftOffer,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>();
|
const ref = useRef<HTMLDivElement>();
|
||||||
@ -155,16 +169,67 @@ const ActionMessage = ({
|
|||||||
const isSingleLine = SINGLE_LINE_ACTIONS.has(action.type);
|
const isSingleLine = SINGLE_LINE_ACTIONS.has(action.type);
|
||||||
const isFluidMultiline = IS_FLUID_BACKGROUND_SUPPORTED && !isSingleLine;
|
const isFluidMultiline = IS_FLUID_BACKGROUND_SUPPORTED && !isSingleLine;
|
||||||
const isClickableText = action.type === 'suggestedPostSuccess';
|
const isClickableText = action.type === 'suggestedPostSuccess';
|
||||||
|
const isNarrowMessage = action.type === 'starGiftPurchaseOfferDeclined';
|
||||||
|
|
||||||
const messageReplyInfo = getMessageReplyInfo(message);
|
const messageReplyInfo = getMessageReplyInfo(message);
|
||||||
const { replyToMsgId, replyToPeerId } = messageReplyInfo || {};
|
const { replyToMsgId, replyToPeerId } = messageReplyInfo || {};
|
||||||
|
|
||||||
const withServiceReactions = Boolean(message.areReactionsPossible && message?.reactions?.results?.length);
|
const withServiceReactions = Boolean(message.areReactionsPossible && message?.reactions?.results?.length);
|
||||||
|
|
||||||
|
const hasGiftOfferExpired = action.type === 'starGiftPurchaseOffer' && action.expiresAt <= getServerTime();
|
||||||
|
const shouldRenderGiftOfferButtons = action.type === 'starGiftPurchaseOffer'
|
||||||
|
&& !message.isOutgoing && !action.isAccepted && !action.isDeclined && !hasGiftOfferExpired;
|
||||||
|
|
||||||
|
const shouldRenderInlineButtons = shouldRenderGiftOfferButtons;
|
||||||
|
|
||||||
const shouldSkipRender = isInsideTopic && action.type === 'topicCreate';
|
const shouldSkipRender = isInsideTopic && action.type === 'topicCreate';
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
const { isTouchScreen } = useAppLayout();
|
const { isTouchScreen } = useAppLayout();
|
||||||
|
|
||||||
|
const giftOfferInlineButtons: KeyboardButtonGiftOffer[][] = useMemo(() => [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: 'giftOffer',
|
||||||
|
buttonType: 'reject',
|
||||||
|
text: lang('GiftOfferReject'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'giftOffer',
|
||||||
|
buttonType: 'accept',
|
||||||
|
text: lang('GiftOfferAccept'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
], [lang]);
|
||||||
|
|
||||||
|
const [isRejectOfferDialogOpen, openRejectOfferDialog, closeRejectOfferDialog] = useFlag(false);
|
||||||
|
|
||||||
|
const handleInlineButtonClick = useLastCallback((button: ApiKeyboardButton) => {
|
||||||
|
if (button.type === 'giftOffer') {
|
||||||
|
if (button.buttonType === 'accept') {
|
||||||
|
if (action.type === 'starGiftPurchaseOffer') {
|
||||||
|
openGiftOfferAcceptModal({
|
||||||
|
peerId: chatId,
|
||||||
|
messageId: id,
|
||||||
|
gift: action.gift,
|
||||||
|
price: action.price,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (button.buttonType === 'reject') {
|
||||||
|
openRejectOfferDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleRejectOfferConfirm = useLastCallback(() => {
|
||||||
|
closeRejectOfferDialog();
|
||||||
|
declineStarGiftOffer({ messageId: id });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleRejectOfferClose = useLastCallback(() => {
|
||||||
|
closeRejectOfferDialog();
|
||||||
|
});
|
||||||
|
|
||||||
useOnIntersect(ref, !shouldSkipRender ? observeIntersectionForBottom : undefined);
|
useOnIntersect(ref, !shouldSkipRender ? observeIntersectionForBottom : undefined);
|
||||||
|
|
||||||
useEnsureMessage(
|
useEnsureMessage(
|
||||||
@ -247,6 +312,13 @@ const ActionMessage = ({
|
|||||||
|
|
||||||
const fluidBackgroundStyle = useFluidBackgroundFilter(isFluidMultiline ? actionMessageBg : undefined);
|
const fluidBackgroundStyle = useFluidBackgroundFilter(isFluidMultiline ? actionMessageBg : undefined);
|
||||||
|
|
||||||
|
const handleKeyDown = useLastCallback((e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleClick();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const handleClick = useLastCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'paymentSent':
|
case 'paymentSent':
|
||||||
@ -309,6 +381,18 @@ const ActionMessage = ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'starGiftPurchaseOffer': {
|
||||||
|
if (shouldRenderGiftOfferButtons) {
|
||||||
|
openGiftOfferAcceptModal({
|
||||||
|
peerId: chatId,
|
||||||
|
messageId: id,
|
||||||
|
gift: action.gift,
|
||||||
|
price: action.price,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'channelJoined': {
|
case 'channelJoined': {
|
||||||
toggleChannelRecommendations({ chatId });
|
toggleChannelRecommendations({ chatId });
|
||||||
break;
|
break;
|
||||||
@ -408,6 +492,18 @@ const ActionMessage = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
case 'starGiftPurchaseOffer':
|
||||||
|
return (
|
||||||
|
<StarGiftPurchaseOffer
|
||||||
|
action={action}
|
||||||
|
message={message}
|
||||||
|
hasButtons={shouldRenderGiftOfferButtons}
|
||||||
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
|
onClick={shouldRenderGiftOfferButtons ? handleClick : undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
case 'channelJoined':
|
case 'channelJoined':
|
||||||
return (
|
return (
|
||||||
<SimilarChannels
|
<SimilarChannels
|
||||||
@ -442,7 +538,9 @@ const ActionMessage = ({
|
|||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}, [action, message, observeIntersectionForLoading, sender, observeIntersectionForPlaying]);
|
}, [
|
||||||
|
action, message, observeIntersectionForLoading, sender, observeIntersectionForPlaying, shouldRenderGiftOfferButtons,
|
||||||
|
]);
|
||||||
|
|
||||||
if ((isInsideTopic && action.type === 'topicCreate') || action.type === 'phoneCall') {
|
if ((isInsideTopic && action.type === 'topicCreate') || action.type === 'phoneCall') {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -473,20 +571,46 @@ const ActionMessage = ({
|
|||||||
{!isTextHidden && (
|
{!isTextHidden && (
|
||||||
<>
|
<>
|
||||||
{isFluidMultiline && (
|
{isFluidMultiline && (
|
||||||
<div className={buildClassName(styles.inlineWrapper, isClickableText && styles.hoverable)}>
|
<div className={buildClassName(
|
||||||
|
styles.inlineWrapper,
|
||||||
|
isClickableText && styles.hoverable,
|
||||||
|
isNarrowMessage && styles.narrowWrapper,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<span className={styles.fluidBackground} style={fluidBackgroundStyle}>
|
<span className={styles.fluidBackground} style={fluidBackgroundStyle}>
|
||||||
<ActionMessageText message={message} isInsideTopic={isInsideTopic} />
|
<ActionMessageText message={message} isInsideTopic={isInsideTopic} />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={buildClassName(styles.inlineWrapper, isClickableText && styles.hoverable)}>
|
<div className={buildClassName(
|
||||||
<span className={styles.textContent} onClick={handleClick}>
|
styles.inlineWrapper,
|
||||||
|
isClickableText && styles.hoverable,
|
||||||
|
isNarrowMessage && styles.narrowWrapper,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={styles.textContent}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={handleClick}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
>
|
||||||
<ActionMessageText message={message} isInsideTopic={isInsideTopic} />
|
<ActionMessageText message={message} isInsideTopic={isInsideTopic} />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{fullContent}
|
{(fullContent || shouldRenderInlineButtons) && (
|
||||||
|
<div className={styles.contentWrapper}>
|
||||||
|
{fullContent}
|
||||||
|
{shouldRenderInlineButtons && (
|
||||||
|
<InlineButtons
|
||||||
|
inlineButtons={giftOfferInlineButtons}
|
||||||
|
onClick={handleInlineButtonClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{contextMenuAnchor && (
|
{contextMenuAnchor && (
|
||||||
<ContextMenuContainer
|
<ContextMenuContainer
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
@ -508,6 +632,19 @@ const ActionMessage = ({
|
|||||||
isAccountFrozen={isAccountFrozen}
|
isAccountFrozen={isAccountFrozen}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={isRejectOfferDialogOpen}
|
||||||
|
title={lang('GiftOfferRejectTitle')}
|
||||||
|
textParts={lang(
|
||||||
|
'GiftOfferRejectText',
|
||||||
|
{ user: sender ? getPeerTitle(lang, sender) : lang('ActionFallbackSomeone') },
|
||||||
|
{ withNodes: true, withMarkdown: true },
|
||||||
|
)}
|
||||||
|
confirmLabel={lang('GiftOfferReject')}
|
||||||
|
confirmIsDestructive
|
||||||
|
confirmHandler={handleRejectOfferConfirm}
|
||||||
|
onClose={handleRejectOfferClose}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import { ensureProtocol } from '../../../util/browser/url';
|
|||||||
import { formatDateTimeToString, formatScheduledDateTime, formatShortDuration } from '../../../util/dates/dateFormat';
|
import { formatDateTimeToString, formatScheduledDateTime, formatShortDuration } from '../../../util/dates/dateFormat';
|
||||||
import { formatCurrency } from '../../../util/formatCurrency';
|
import { formatCurrency } from '../../../util/formatCurrency';
|
||||||
import { convertTonFromNanos } from '../../../util/formatCurrency';
|
import { convertTonFromNanos } from '../../../util/formatCurrency';
|
||||||
import { formatStarsAsText, formatTonAsText } from '../../../util/localization/format';
|
import { formatCurrencyAmountAsText, formatStarsAsText, formatTonAsText } from '../../../util/localization/format';
|
||||||
import { conjuctionWithNodes } from '../../../util/localization/utils';
|
import { conjuctionWithNodes } from '../../../util/localization/utils';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
@ -616,6 +616,7 @@ const ActionMessageText = ({
|
|||||||
case 'starGiftUnique': {
|
case 'starGiftUnique': {
|
||||||
const {
|
const {
|
||||||
isTransferred, isUpgrade, savedId, peerId, fromId, resaleAmount, gift, transferStars, isPrepaidUpgrade,
|
isTransferred, isUpgrade, savedId, peerId, fromId, resaleAmount, gift, transferStars, isPrepaidUpgrade,
|
||||||
|
isFromOffer,
|
||||||
} = action;
|
} = action;
|
||||||
|
|
||||||
const isToChannel = Boolean(peerId && savedId);
|
const isToChannel = Boolean(peerId && savedId);
|
||||||
@ -629,6 +630,28 @@ const ActionMessageText = ({
|
|||||||
|| (isToChannel ? channelFallbackText : userFallbackText);
|
|| (isToChannel ? channelFallbackText : userFallbackText);
|
||||||
const toLink = renderPeerLink(toPeer?.id, toTitle, asPreview);
|
const toLink = renderPeerLink(toPeer?.id, toTitle, asPreview);
|
||||||
|
|
||||||
|
if (isFromOffer && resaleAmount) {
|
||||||
|
const giftName = lang('GiftUnique', { title: gift.title, number: gift.number });
|
||||||
|
const amountText = formatCurrencyAmountAsText(lang, resaleAmount);
|
||||||
|
|
||||||
|
const formattedAmountText = asPreview ? amountText : renderStrong(amountText);
|
||||||
|
const formattedGiftName = asPreview ? giftName : renderStrong(giftName);
|
||||||
|
|
||||||
|
if (isOutgoing) {
|
||||||
|
return lang(
|
||||||
|
'ActionStarGiftSoldFromOffer',
|
||||||
|
{ user: chatLink, gift: formattedGiftName, cost: formattedAmountText },
|
||||||
|
{ withNodes: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lang(
|
||||||
|
'ActionStarGiftBoughtFromOffer',
|
||||||
|
{ user: senderLink, gift: formattedGiftName, cost: formattedAmountText },
|
||||||
|
{ withNodes: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isPrepaidUpgrade) {
|
if (isPrepaidUpgrade) {
|
||||||
if (isOutgoing) {
|
if (isOutgoing) {
|
||||||
return lang('ActionStarGiftPrepaidUpgradedYou');
|
return lang('ActionStarGiftPrepaidUpgradedYou');
|
||||||
@ -637,9 +660,7 @@ const ActionMessageText = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resaleAmount && !transferStars) {
|
if (resaleAmount && !transferStars) {
|
||||||
const amountText = resaleAmount.currency === TON_CURRENCY_CODE
|
const amountText = formatCurrencyAmountAsText(lang, resaleAmount);
|
||||||
? formatTonAsText(lang, convertTonFromNanos(resaleAmount.amount))
|
|
||||||
: formatStarsAsText(lang, resaleAmount.amount);
|
|
||||||
|
|
||||||
return lang(
|
return lang(
|
||||||
isOutgoing
|
isOutgoing
|
||||||
@ -1023,6 +1044,55 @@ const ActionMessageText = ({
|
|||||||
|
|
||||||
case 'phoneCall': // Rendered as a regular message, but considered an action for the summary
|
case 'phoneCall': // Rendered as a regular message, but considered an action for the summary
|
||||||
return lang(getCallMessageKey(action, isOutgoing));
|
return lang(getCallMessageKey(action, isOutgoing));
|
||||||
|
|
||||||
|
case 'starGiftPurchaseOffer': {
|
||||||
|
const { gift, price } = action;
|
||||||
|
|
||||||
|
const peer = isOutgoing ? chat : sender;
|
||||||
|
const peerTitle = (peer && getPeerTitle(lang, peer)) || userFallbackText;
|
||||||
|
const peerLink = renderPeerLink(peer?.id, peerTitle, asPreview);
|
||||||
|
|
||||||
|
const giftName = lang('GiftUnique', { title: gift.title, number: gift.number });
|
||||||
|
const priceText = formatCurrencyAmountAsText(lang, price);
|
||||||
|
|
||||||
|
const formattedGiftName = asPreview ? giftName : renderStrong(giftName);
|
||||||
|
const formattedPriceText = asPreview ? priceText : renderStrong(priceText);
|
||||||
|
|
||||||
|
return lang(
|
||||||
|
isOutgoing ? 'ActionStarGiftOfferOutgoing' : 'ActionStarGiftOfferIncoming',
|
||||||
|
{
|
||||||
|
peer: peerLink,
|
||||||
|
cost: formattedPriceText,
|
||||||
|
gift: formattedGiftName,
|
||||||
|
},
|
||||||
|
{ withNodes: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'starGiftPurchaseOfferDeclined': {
|
||||||
|
const { gift, price } = action;
|
||||||
|
|
||||||
|
const peer = isOutgoing ? chat : sender;
|
||||||
|
const peerTitle = (peer && getPeerTitle(lang, peer)) || userFallbackText;
|
||||||
|
const peerLink = renderPeerLink(peer?.id, peerTitle, asPreview);
|
||||||
|
|
||||||
|
const giftName = lang('GiftUnique', { title: gift.title, number: gift.number });
|
||||||
|
const priceText = formatCurrencyAmountAsText(lang, price);
|
||||||
|
|
||||||
|
const formattedGiftName = asPreview ? giftName : renderStrong(giftName);
|
||||||
|
const formattedPriceText = asPreview ? priceText : renderStrong(priceText);
|
||||||
|
|
||||||
|
return lang(
|
||||||
|
isOutgoing ? 'ActionStarGiftOfferDeclinedOutgoing' : 'ActionStarGiftOfferDeclinedIncoming',
|
||||||
|
{
|
||||||
|
peer: peerLink,
|
||||||
|
gift: formattedGiftName,
|
||||||
|
cost: formattedPriceText,
|
||||||
|
},
|
||||||
|
{ withNodes: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return lang(UNSUPPORTED_LANG_KEY);
|
return lang(UNSUPPORTED_LANG_KEY);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
transition: background-color 150ms, color 150ms, backdrop-filter 150ms, filter 150ms;
|
transition: background-color 150ms, color 150ms, backdrop-filter 150ms, filter 150ms;
|
||||||
|
|
||||||
|
&:active,
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background: var(--action-message-bg) !important;
|
background: var(--action-message-bg) !important;
|
||||||
@ -61,6 +62,7 @@
|
|||||||
|
|
||||||
.left-icon {
|
.left-icon {
|
||||||
margin-right: 0.25rem;
|
margin-right: 0.25rem;
|
||||||
|
font-size: 1rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline-button-text {
|
.inline-button-text {
|
||||||
|
|||||||
@ -58,6 +58,14 @@ const InlineButtons = ({ inlineButtons, onClick }: OwnProps) => {
|
|||||||
return <Icon className="left-icon" name="close" />;
|
return <Icon className="left-icon" name="close" />;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'giftOffer':
|
||||||
|
if (button.buttonType === 'accept') {
|
||||||
|
return <Icon className="left-icon" name="check" />;
|
||||||
|
}
|
||||||
|
if (button.buttonType === 'reject') {
|
||||||
|
return <Icon className="left-icon" name="close" />;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -0,0 +1,63 @@
|
|||||||
|
.root {
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
&.hasButtons {
|
||||||
|
border-bottom-right-radius: var(--border-radius-messages-small);
|
||||||
|
border-bottom-left-radius: var(--border-radius-messages-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.clickable {
|
||||||
|
cursor: var(--custom-cursor, pointer);
|
||||||
|
transition: background-color 150ms, backdrop-filter 150ms, filter 150ms;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background: var(--action-message-bg) !important;
|
||||||
|
backdrop-filter: brightness(115%);
|
||||||
|
|
||||||
|
@supports not (backdrop-filter: brightness(115%)) {
|
||||||
|
filter: brightness(115%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.giftContainer {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 4.75rem;
|
||||||
|
height: 4.75rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniqueBackgroundWrapper,
|
||||||
|
.uniqueBackground {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stickerWrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin-block: 0.25rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
180
src/components/middle/message/actions/StarGiftPurchaseOffer.tsx
Normal file
180
src/components/middle/message/actions/StarGiftPurchaseOffer.tsx
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import { memo, useMemo, useRef } from '@teact';
|
||||||
|
import { withGlobal } from '../../../../global';
|
||||||
|
|
||||||
|
import type { ApiMessage, ApiPeer } from '../../../../api/types';
|
||||||
|
import type { ApiMessageActionStarGiftPurchaseOffer } from '../../../../api/types/messageActions';
|
||||||
|
|
||||||
|
import { getPeerTitle } from '../../../../global/helpers/peers';
|
||||||
|
import {
|
||||||
|
selectCanPlayAnimatedEmojis,
|
||||||
|
selectPeer,
|
||||||
|
selectSender,
|
||||||
|
selectUser,
|
||||||
|
} from '../../../../global/selectors';
|
||||||
|
import { IS_TOUCH_ENV } from '../../../../util/browser/windowEnvironment';
|
||||||
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
|
import { formatShortHoursMinutes } from '../../../../util/dates/dateFormat';
|
||||||
|
import { formatCurrencyAmountAsText } from '../../../../util/localization/format';
|
||||||
|
import { getServerTime } from '../../../../util/serverTime';
|
||||||
|
import { getGiftAttributes, getStickerFromGift } from '../../../common/helpers/gifts';
|
||||||
|
import { renderPeerLink } from '../helpers/messageActions';
|
||||||
|
|
||||||
|
import useIntervalForceUpdate from '../../../../hooks/schedulers/useIntervalForceUpdate';
|
||||||
|
import useFlag from '../../../../hooks/useFlag';
|
||||||
|
import { type ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
||||||
|
import useLang from '../../../../hooks/useLang';
|
||||||
|
import useOldLang from '../../../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import RadialPatternBackground from '../../../common/profile/RadialPatternBackground';
|
||||||
|
import StickerView from '../../../common/StickerView';
|
||||||
|
|
||||||
|
import actionStyles from '../ActionMessage.module.scss';
|
||||||
|
import styles from './StarGiftPurchaseOffer.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
message: ApiMessage;
|
||||||
|
action: ApiMessageActionStarGiftPurchaseOffer;
|
||||||
|
hasButtons?: boolean;
|
||||||
|
observeIntersectionForLoading?: ObserveFn;
|
||||||
|
observeIntersectionForPlaying?: ObserveFn;
|
||||||
|
onClick?: NoneToVoidFunction;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
canPlayAnimatedEmojis: boolean;
|
||||||
|
sender?: ApiPeer;
|
||||||
|
recipient?: ApiPeer;
|
||||||
|
};
|
||||||
|
|
||||||
|
const STICKER_SIZE = 48;
|
||||||
|
const ONE_MINUTE = 60 * 1000;
|
||||||
|
|
||||||
|
const StarGiftPurchaseOffer = ({
|
||||||
|
action,
|
||||||
|
message,
|
||||||
|
hasButtons,
|
||||||
|
canPlayAnimatedEmojis,
|
||||||
|
sender,
|
||||||
|
recipient,
|
||||||
|
observeIntersectionForLoading,
|
||||||
|
observeIntersectionForPlaying,
|
||||||
|
onClick,
|
||||||
|
}: OwnProps & StateProps) => {
|
||||||
|
const stickerRef = useRef<HTMLDivElement>();
|
||||||
|
const lang = useLang();
|
||||||
|
const oldLang = useOldLang();
|
||||||
|
|
||||||
|
const [isHover, markHover, unmarkHover] = useFlag();
|
||||||
|
|
||||||
|
const { isOutgoing } = message;
|
||||||
|
|
||||||
|
const sticker = getStickerFromGift(action.gift);
|
||||||
|
const attributes = getGiftAttributes(action.gift);
|
||||||
|
const pattern = attributes?.pattern;
|
||||||
|
const backdrop = attributes?.backdrop;
|
||||||
|
|
||||||
|
const isActive = !action.isAccepted && !action.isDeclined;
|
||||||
|
useIntervalForceUpdate(isActive ? ONE_MINUTE : undefined);
|
||||||
|
|
||||||
|
const serverTime = getServerTime();
|
||||||
|
const timeLeft = Math.max(0, action.expiresAt - serverTime);
|
||||||
|
const formattedTime = formatShortHoursMinutes(oldLang, timeLeft);
|
||||||
|
const hasExpired = timeLeft <= 0;
|
||||||
|
|
||||||
|
const subtitle = useMemo(() => {
|
||||||
|
if (action.isAccepted) return lang('ActionStarGiftOfferAccepted');
|
||||||
|
if (action.isDeclined) return lang('ActionStarGiftOfferRejected');
|
||||||
|
if (hasExpired) return lang('ActionStarGiftOfferHasExpired');
|
||||||
|
return lang('ActionStarGiftOfferExpires', { time: formattedTime });
|
||||||
|
}, [action.isAccepted, action.isDeclined, formattedTime, lang, hasExpired]);
|
||||||
|
|
||||||
|
if (!sticker || !pattern || !backdrop) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const backgroundColors = [backdrop.centerColor, backdrop.edgeColor];
|
||||||
|
|
||||||
|
const peer = isOutgoing ? recipient : sender;
|
||||||
|
const fallbackPeerTitle = lang('ActionFallbackSomeone');
|
||||||
|
const peerTitle = peer && getPeerTitle(lang, peer);
|
||||||
|
|
||||||
|
const giftName = lang('GiftUnique', { title: action.gift.title, number: action.gift.number });
|
||||||
|
const priceText = formatCurrencyAmountAsText(lang, action.price);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={buildClassName(
|
||||||
|
actionStyles.contentBox,
|
||||||
|
styles.root,
|
||||||
|
hasButtons && styles.hasButtons,
|
||||||
|
onClick && styles.clickable,
|
||||||
|
)}
|
||||||
|
tabIndex={onClick ? 0 : undefined}
|
||||||
|
role={onClick ? 'button' : undefined}
|
||||||
|
onMouseEnter={!IS_TOUCH_ENV ? markHover : undefined}
|
||||||
|
onMouseLeave={!IS_TOUCH_ENV ? unmarkHover : undefined}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<div className={styles.giftContainer}>
|
||||||
|
<div className={styles.uniqueBackgroundWrapper}>
|
||||||
|
<RadialPatternBackground
|
||||||
|
className={styles.uniqueBackground}
|
||||||
|
backgroundColors={backgroundColors}
|
||||||
|
patternIcon={pattern.sticker}
|
||||||
|
patternSize={9}
|
||||||
|
ringsCount={1}
|
||||||
|
ovalFactor={1}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref={stickerRef}
|
||||||
|
className={styles.stickerWrapper}
|
||||||
|
style={`width: ${STICKER_SIZE}px; height: ${STICKER_SIZE}px`}
|
||||||
|
>
|
||||||
|
{sticker && (
|
||||||
|
<StickerView
|
||||||
|
containerRef={stickerRef}
|
||||||
|
sticker={sticker}
|
||||||
|
size={STICKER_SIZE}
|
||||||
|
shouldLoop={isHover}
|
||||||
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
|
noLoad={!canPlayAnimatedEmojis}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.info}>
|
||||||
|
<p className={styles.title}>
|
||||||
|
{lang(
|
||||||
|
isOutgoing ? 'ActionStarGiftOfferOutgoing' : 'ActionStarGiftOfferIncoming',
|
||||||
|
{
|
||||||
|
peer: renderPeerLink(peer?.id, peerTitle || fallbackPeerTitle),
|
||||||
|
cost: priceText,
|
||||||
|
gift: giftName,
|
||||||
|
},
|
||||||
|
{ withNodes: true, withMarkdown: true },
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p className={styles.subtitle}>
|
||||||
|
{subtitle}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { message }): Complete<StateProps> => {
|
||||||
|
const currentUser = selectUser(global, global.currentUserId!);
|
||||||
|
const canPlayAnimatedEmojis = selectCanPlayAnimatedEmojis(global);
|
||||||
|
const messageSender = selectSender(global, message);
|
||||||
|
const messageRecipient = message.isOutgoing ? selectPeer(global, message.chatId) : currentUser;
|
||||||
|
|
||||||
|
return {
|
||||||
|
canPlayAnimatedEmojis,
|
||||||
|
sender: messageSender,
|
||||||
|
recipient: messageRecipient,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(StarGiftPurchaseOffer));
|
||||||
@ -29,6 +29,7 @@ import PremiumGiftModal from './gift/GiftModal.async';
|
|||||||
import GiftInfoModal from './gift/info/GiftInfoModal.async';
|
import GiftInfoModal from './gift/info/GiftInfoModal.async';
|
||||||
import GiftLockedModal from './gift/locked/GiftLockedModal.async';
|
import GiftLockedModal from './gift/locked/GiftLockedModal.async';
|
||||||
import GiftDescriptionRemoveModal from './gift/message/GiftDescriptionRemoveModal.async';
|
import GiftDescriptionRemoveModal from './gift/message/GiftDescriptionRemoveModal.async';
|
||||||
|
import GiftOfferAcceptModal from './gift/offer/GiftOfferAcceptModal.async';
|
||||||
import GiftRecipientPicker from './gift/recipient/GiftRecipientPicker.async';
|
import GiftRecipientPicker from './gift/recipient/GiftRecipientPicker.async';
|
||||||
import GiftResalePriceComposerModal from './gift/resale/GiftResalePriceComposerModal.async';
|
import GiftResalePriceComposerModal from './gift/resale/GiftResalePriceComposerModal.async';
|
||||||
import StarGiftPriceDecreaseInfoModal from './gift/StarGiftPriceDecreaseInfoModal.async';
|
import StarGiftPriceDecreaseInfoModal from './gift/StarGiftPriceDecreaseInfoModal.async';
|
||||||
@ -115,6 +116,7 @@ type ModalKey = keyof Pick<TabState,
|
|||||||
'giftTransferModal' |
|
'giftTransferModal' |
|
||||||
'giftTransferConfirmModal' |
|
'giftTransferConfirmModal' |
|
||||||
'giftDescriptionRemoveModal' |
|
'giftDescriptionRemoveModal' |
|
||||||
|
'giftOfferAcceptModal' |
|
||||||
'chatRefundModal' |
|
'chatRefundModal' |
|
||||||
'priceConfirmModal' |
|
'priceConfirmModal' |
|
||||||
'isFrozenAccountModalOpen' |
|
'isFrozenAccountModalOpen' |
|
||||||
@ -188,6 +190,7 @@ const MODALS: ModalRegistry = {
|
|||||||
giftTransferModal: GiftTransferModal,
|
giftTransferModal: GiftTransferModal,
|
||||||
giftTransferConfirmModal: GiftTransferConfirmModal,
|
giftTransferConfirmModal: GiftTransferConfirmModal,
|
||||||
giftDescriptionRemoveModal: GiftDescriptionRemoveModal,
|
giftDescriptionRemoveModal: GiftDescriptionRemoveModal,
|
||||||
|
giftOfferAcceptModal: GiftOfferAcceptModal,
|
||||||
chatRefundModal: ChatRefundModal,
|
chatRefundModal: ChatRefundModal,
|
||||||
priceConfirmModal: PriceConfirmModal,
|
priceConfirmModal: PriceConfirmModal,
|
||||||
isFrozenAccountModalOpen: FrozenAccountModal,
|
isFrozenAccountModalOpen: FrozenAccountModal,
|
||||||
|
|||||||
@ -743,16 +743,11 @@ const GiftInfoModal = ({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (gift.valueAmount && gift.valueCurrency) {
|
if (gift.valueAmount && gift.valueCurrency) {
|
||||||
|
const formattedValue = formatCurrencyAsString(gift.valueAmount, gift.valueCurrency, lang.code);
|
||||||
tableData.push([
|
tableData.push([
|
||||||
lang('GiftInfoValue'),
|
lang('GiftInfoValue'),
|
||||||
<span className={styles.uniqueValue}>
|
<span className={styles.uniqueValue}>
|
||||||
~
|
{lang('GiftInfoValueAmount', { amount: formattedValue })}
|
||||||
{' '}
|
|
||||||
{formatCurrencyAsString(
|
|
||||||
gift.valueAmount,
|
|
||||||
gift.valueCurrency,
|
|
||||||
lang.code,
|
|
||||||
)}
|
|
||||||
<BadgeButton onClick={handleOpenValueModal}>
|
<BadgeButton onClick={handleOpenValueModal}>
|
||||||
{lang('GiftInfoValueLinkMore')}
|
{lang('GiftInfoValueLinkMore')}
|
||||||
</BadgeButton>
|
</BadgeButton>
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
import type { OwnProps } from './GiftOfferAcceptModal';
|
||||||
|
|
||||||
|
import { Bundles } from '../../../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const GiftOfferAcceptModalAsync = (props: OwnProps) => {
|
||||||
|
const { modal } = props;
|
||||||
|
const GiftOfferAcceptModal = useModuleLoader(
|
||||||
|
Bundles.Stars,
|
||||||
|
'GiftOfferAcceptModal',
|
||||||
|
!modal,
|
||||||
|
);
|
||||||
|
|
||||||
|
return GiftOfferAcceptModal ? <GiftOfferAcceptModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GiftOfferAcceptModalAsync;
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
.description {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
text-align: center;
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receiveText {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributeValue {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--color-error);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--color-success);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
226
src/components/modals/gift/offer/GiftOfferAcceptModal.tsx
Normal file
226
src/components/modals/gift/offer/GiftOfferAcceptModal.tsx
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
import { memo, useMemo } from '../../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
|
import type { ApiPeer } from '../../../../api/types';
|
||||||
|
import type { TabState } from '../../../../global/types';
|
||||||
|
|
||||||
|
import { TON_CURRENCY_CODE } from '../../../../config';
|
||||||
|
import { getPeerTitle } from '../../../../global/helpers/peers';
|
||||||
|
import {
|
||||||
|
selectPeer,
|
||||||
|
selectStarsGiftResaleCommission,
|
||||||
|
selectTonGiftResaleCommission,
|
||||||
|
} from '../../../../global/selectors';
|
||||||
|
import { convertTonToUsd, formatCurrencyAsString } from '../../../../util/formatCurrency';
|
||||||
|
import {
|
||||||
|
formatCurrencyAmountAsText, formatStarsAsIcon, formatStarsAsText, formatTonAsIcon, formatTonAsText,
|
||||||
|
} from '../../../../util/localization/format';
|
||||||
|
import { round } from '../../../../util/math';
|
||||||
|
import { formatPercent } from '../../../../util/textFormat';
|
||||||
|
import { getGiftAttributes } from '../../../common/helpers/gifts';
|
||||||
|
|
||||||
|
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||||
|
import useLang from '../../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import BadgeButton from '../../../common/BadgeButton';
|
||||||
|
import GiftTransferPreview from '../../../common/gift/GiftTransferPreview';
|
||||||
|
import ConfirmDialog from '../../../ui/ConfirmDialog';
|
||||||
|
import TableInfo, { type TableData } from '../../common/TableInfo';
|
||||||
|
|
||||||
|
import styles from './GiftOfferAcceptModal.module.scss';
|
||||||
|
|
||||||
|
const PRICE_WARNING_THRESHOLD_PERCENT = 10;
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
modal: TabState['giftOfferAcceptModal'];
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
recipientPeer?: ApiPeer;
|
||||||
|
starsCommission?: number;
|
||||||
|
tonCommission?: number;
|
||||||
|
starsUsdRate?: number;
|
||||||
|
tonUsdRate?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const GiftOfferAcceptModal = ({
|
||||||
|
modal, recipientPeer, starsCommission, tonCommission, starsUsdRate, tonUsdRate,
|
||||||
|
}: OwnProps & StateProps) => {
|
||||||
|
const {
|
||||||
|
closeGiftOfferAcceptModal, acceptStarGiftOffer,
|
||||||
|
} = getActions();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const isOpen = Boolean(modal);
|
||||||
|
const renderingModal = useCurrentOrPrev(modal);
|
||||||
|
const renderingBuyerPeer = useCurrentOrPrev(recipientPeer);
|
||||||
|
|
||||||
|
const handleConfirm = useLastCallback(() => {
|
||||||
|
if (!renderingModal) return;
|
||||||
|
|
||||||
|
acceptStarGiftOffer({ messageId: renderingModal.messageId });
|
||||||
|
closeGiftOfferAcceptModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
const giftAttributes = useMemo(() => {
|
||||||
|
return renderingModal?.gift && getGiftAttributes(renderingModal.gift);
|
||||||
|
}, [renderingModal?.gift]);
|
||||||
|
|
||||||
|
const isPriceInTon = renderingModal?.price.currency === TON_CURRENCY_CODE;
|
||||||
|
const commission = isPriceInTon ? tonCommission : starsCommission;
|
||||||
|
const priceAmount = renderingModal?.price.amount || 0;
|
||||||
|
const receiveAmount = commission
|
||||||
|
? (round(priceAmount * commission, isPriceInTon ? 2 : 0))
|
||||||
|
: priceAmount;
|
||||||
|
|
||||||
|
const tableData: TableData = useMemo(() => {
|
||||||
|
if (!giftAttributes) return [];
|
||||||
|
|
||||||
|
const { model, backdrop, pattern } = giftAttributes;
|
||||||
|
const data: TableData = [];
|
||||||
|
|
||||||
|
if (model) {
|
||||||
|
data.push([
|
||||||
|
lang('GiftAttributeModel'),
|
||||||
|
<span className={styles.attributeValue}>
|
||||||
|
<span>{model.name}</span>
|
||||||
|
<BadgeButton>{formatPercent(model.rarityPercent)}</BadgeButton>
|
||||||
|
</span>,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backdrop) {
|
||||||
|
data.push([
|
||||||
|
lang('GiftAttributeBackdrop'),
|
||||||
|
<span className={styles.attributeValue}>
|
||||||
|
<span>{backdrop.name}</span>
|
||||||
|
<BadgeButton>{formatPercent(backdrop.rarityPercent)}</BadgeButton>
|
||||||
|
</span>,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern) {
|
||||||
|
data.push([
|
||||||
|
lang('GiftAttributeSymbol'),
|
||||||
|
<span className={styles.attributeValue}>
|
||||||
|
<span>{pattern.name}</span>
|
||||||
|
<BadgeButton>{formatPercent(pattern.rarityPercent)}</BadgeButton>
|
||||||
|
</span>,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gift = renderingModal?.gift;
|
||||||
|
if (gift?.valueAmount && gift.valueCurrency) {
|
||||||
|
const formattedValue = formatCurrencyAsString(gift.valueAmount, gift.valueCurrency, lang.code);
|
||||||
|
data.push([
|
||||||
|
lang('GiftInfoValue'),
|
||||||
|
lang('GiftInfoValueAmount', { amount: formattedValue }),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}, [giftAttributes, lang, renderingModal?.gift]);
|
||||||
|
|
||||||
|
const priceWarning = useMemo(() => {
|
||||||
|
if (!renderingModal) return undefined;
|
||||||
|
|
||||||
|
const { gift } = renderingModal;
|
||||||
|
const { valueUsdAmount } = gift;
|
||||||
|
if (!valueUsdAmount || valueUsdAmount <= 0 || receiveAmount <= 0) return undefined;
|
||||||
|
|
||||||
|
const avgValueUsd = valueUsdAmount / 100;
|
||||||
|
|
||||||
|
let receiveValueUsd: number;
|
||||||
|
if (isPriceInTon) {
|
||||||
|
if (!tonUsdRate) return undefined;
|
||||||
|
receiveValueUsd = convertTonToUsd(receiveAmount, tonUsdRate, true) / 100;
|
||||||
|
} else {
|
||||||
|
if (!starsUsdRate) return undefined;
|
||||||
|
receiveValueUsd = receiveAmount * starsUsdRate / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLower = avgValueUsd >= receiveValueUsd;
|
||||||
|
const percent = isLower
|
||||||
|
? (1 - receiveValueUsd / avgValueUsd) * 100
|
||||||
|
: (receiveValueUsd / avgValueUsd - 1) * 100;
|
||||||
|
|
||||||
|
if (percent <= PRICE_WARNING_THRESHOLD_PERCENT) return undefined;
|
||||||
|
|
||||||
|
return { percent, isLow: isLower };
|
||||||
|
}, [renderingModal, receiveAmount, isPriceInTon, tonUsdRate, starsUsdRate]);
|
||||||
|
|
||||||
|
if (!renderingModal || !renderingBuyerPeer) return undefined;
|
||||||
|
|
||||||
|
const { gift, price } = renderingModal;
|
||||||
|
const giftName = lang('GiftUnique', { title: gift.title, number: gift.number });
|
||||||
|
const buyerName = getPeerTitle(lang, renderingBuyerPeer);
|
||||||
|
|
||||||
|
const formattedPrice = formatCurrencyAmountAsText(lang, price);
|
||||||
|
const formattedReceiveAmountAsText = isPriceInTon
|
||||||
|
? formatTonAsText(lang, receiveAmount, true)
|
||||||
|
: formatStarsAsText(lang, receiveAmount);
|
||||||
|
const formattedReceiveAmountAsIcon = isPriceInTon
|
||||||
|
? formatTonAsIcon(lang, receiveAmount, { shouldConvertFromNanos: true })
|
||||||
|
: formatStarsAsIcon(lang, receiveAmount, { asFont: true });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={isOpen}
|
||||||
|
title={lang('GiftOfferAcceptTitle')}
|
||||||
|
onClose={closeGiftOfferAcceptModal}
|
||||||
|
confirmLabel={lang('GiftOfferAcceptButton', {
|
||||||
|
amount: formattedReceiveAmountAsIcon,
|
||||||
|
}, { withNodes: true })}
|
||||||
|
confirmHandler={handleConfirm}
|
||||||
|
>
|
||||||
|
<GiftTransferPreview
|
||||||
|
peer={renderingBuyerPeer}
|
||||||
|
gift={gift}
|
||||||
|
/>
|
||||||
|
<p className={styles.description}>
|
||||||
|
{lang('GiftOfferAcceptText', {
|
||||||
|
gift: giftName,
|
||||||
|
user: buyerName,
|
||||||
|
price: formattedPrice,
|
||||||
|
}, { withNodes: true, withMarkdown: true })}
|
||||||
|
</p>
|
||||||
|
<p className={styles.receiveText}>
|
||||||
|
{lang('GiftOfferAcceptReceive', {
|
||||||
|
amount: formattedReceiveAmountAsText,
|
||||||
|
}, { withNodes: true, withMarkdown: true })}
|
||||||
|
</p>
|
||||||
|
{Boolean(tableData.length) && (
|
||||||
|
<TableInfo tableData={tableData} className={styles.table} />
|
||||||
|
)}
|
||||||
|
{priceWarning && (
|
||||||
|
<p className={priceWarning.isLow ? styles.warning : styles.success}>
|
||||||
|
{lang(priceWarning.isLow ? 'GiftOfferPriceLow' : 'GiftOfferPriceHigh', {
|
||||||
|
percent: formatPercent(priceWarning.percent, 0),
|
||||||
|
gift: gift.title,
|
||||||
|
}, { withNodes: true, withMarkdown: true })}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</ConfirmDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(
|
||||||
|
withGlobal<OwnProps>((global, { modal }): Complete<StateProps> => {
|
||||||
|
const recipientPeer = modal?.peerId ? selectPeer(global, modal.peerId) : undefined;
|
||||||
|
const starsCommission = selectStarsGiftResaleCommission(global);
|
||||||
|
const tonCommission = selectTonGiftResaleCommission(global);
|
||||||
|
|
||||||
|
const starsUsdSellRateX1000 = global.appConfig?.starsUsdSellRateX1000;
|
||||||
|
const starsUsdRate = starsUsdSellRateX1000 ? starsUsdSellRateX1000 / 1000 : undefined;
|
||||||
|
const tonUsdRate = global.appConfig?.tonUsdRate;
|
||||||
|
|
||||||
|
return {
|
||||||
|
recipientPeer,
|
||||||
|
starsCommission,
|
||||||
|
tonCommission,
|
||||||
|
starsUsdRate,
|
||||||
|
tonUsdRate,
|
||||||
|
};
|
||||||
|
})(GiftOfferAcceptModal),
|
||||||
|
);
|
||||||
@ -5,6 +5,10 @@ import { getActions, withGlobal } from '../../../../global';
|
|||||||
|
|
||||||
import type { TabState } from '../../../../global/types';
|
import type { TabState } from '../../../../global/types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
selectStarsGiftResaleCommission,
|
||||||
|
selectTonGiftResaleCommission,
|
||||||
|
} from '../../../../global/selectors';
|
||||||
import { convertTonFromNanos, convertTonToNanos } from '../../../../util/formatCurrency';
|
import { convertTonFromNanos, convertTonToNanos } from '../../../../util/formatCurrency';
|
||||||
import { convertTonToUsd, formatCurrencyAsString } from '../../../../util/formatCurrency';
|
import { convertTonToUsd, formatCurrencyAsString } from '../../../../util/formatCurrency';
|
||||||
import { formatStarsAsIcon, formatStarsAsText, formatTonAsIcon,
|
import { formatStarsAsIcon, formatStarsAsText, formatTonAsIcon,
|
||||||
@ -26,20 +30,20 @@ export type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type StateProps = {
|
export type StateProps = {
|
||||||
starsStargiftResaleCommissionPermille?: number;
|
starsCommission?: number;
|
||||||
starsStargiftResaleAmountMin: number;
|
starsResaleAmountMin: number;
|
||||||
starsStargiftResaleAmountMax?: number;
|
starsResaleAmountMax?: number;
|
||||||
starsUsdWithdrawRate?: number;
|
starsUsdWithdrawRate?: number;
|
||||||
tonStargiftResaleCommissionPermille?: number;
|
tonCommission?: number;
|
||||||
tonStargiftResaleAmountMin: number;
|
tonResaleAmountMin: number;
|
||||||
tonStargiftResaleAmountMax?: number;
|
tonResaleAmountMax?: number;
|
||||||
tonUsdRate?: number;
|
tonUsdRate?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GiftResalePriceComposerModal = ({
|
const GiftResalePriceComposerModal = ({
|
||||||
modal, starsStargiftResaleCommissionPermille,
|
modal, starsCommission,
|
||||||
starsStargiftResaleAmountMin, starsStargiftResaleAmountMax, starsUsdWithdrawRate,
|
starsResaleAmountMin, starsResaleAmountMax, starsUsdWithdrawRate,
|
||||||
tonStargiftResaleCommissionPermille, tonStargiftResaleAmountMin, tonStargiftResaleAmountMax, tonUsdRate,
|
tonCommission, tonResaleAmountMin, tonResaleAmountMax, tonUsdRate,
|
||||||
}: OwnProps & StateProps) => {
|
}: OwnProps & StateProps) => {
|
||||||
const {
|
const {
|
||||||
closeGiftResalePriceComposerModal,
|
closeGiftResalePriceComposerModal,
|
||||||
@ -62,7 +66,7 @@ const GiftResalePriceComposerModal = ({
|
|||||||
const handleChangePrice = useLastCallback((e) => {
|
const handleChangePrice = useLastCallback((e) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
const number = parseFloat(value);
|
const number = parseFloat(value);
|
||||||
const maxAmount = isPriceInTon ? tonStargiftResaleAmountMax : starsStargiftResaleAmountMax;
|
const maxAmount = isPriceInTon ? tonResaleAmountMax : starsResaleAmountMax;
|
||||||
const result = value === '' || Number.isNaN(number) ? undefined
|
const result = value === '' || Number.isNaN(number) ? undefined
|
||||||
: maxAmount ? Math.min(number, maxAmount) : number;
|
: maxAmount ? Math.min(number, maxAmount) : number;
|
||||||
setPrice(result);
|
setPrice(result);
|
||||||
@ -95,8 +99,8 @@ const GiftResalePriceComposerModal = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const commission = isPriceInTon ? tonStargiftResaleCommissionPermille : starsStargiftResaleCommissionPermille;
|
const commission = isPriceInTon ? tonCommission : starsCommission;
|
||||||
const minAmount = isPriceInTon ? tonStargiftResaleAmountMin : starsStargiftResaleAmountMin;
|
const minAmount = isPriceInTon ? tonResaleAmountMin : starsResaleAmountMin;
|
||||||
const isPriceCorrect = hasPrice && price >= minAmount;
|
const isPriceCorrect = hasPrice && price >= minAmount;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -176,30 +180,28 @@ const GiftResalePriceComposerModal = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): Complete<StateProps> => {
|
(global): Complete<StateProps> => {
|
||||||
const configPermille = global.appConfig.starsStargiftResaleCommissionPermille;
|
const starsCommission = selectStarsGiftResaleCommission(global);
|
||||||
const starsStargiftResaleCommissionPermille = configPermille ? (configPermille / 1000) : undefined;
|
const starsResaleAmountMin = global.appConfig.starsStargiftResaleAmountMin || 0;
|
||||||
const starsStargiftResaleAmountMin = global.appConfig.starsStargiftResaleAmountMin || 0;
|
const starsResaleAmountMax = global.appConfig.starsStargiftResaleAmountMax;
|
||||||
const starsStargiftResaleAmountMax = global.appConfig.starsStargiftResaleAmountMax;
|
|
||||||
|
|
||||||
const starsUsdWithdrawRateX1000 = global.appConfig.starsUsdWithdrawRateX1000;
|
const starsUsdWithdrawRateX1000 = global.appConfig.starsUsdWithdrawRateX1000;
|
||||||
const starsUsdWithdrawRate = starsUsdWithdrawRateX1000 ? starsUsdWithdrawRateX1000 / 1000 : 1;
|
const starsUsdWithdrawRate = starsUsdWithdrawRateX1000 ? starsUsdWithdrawRateX1000 / 1000 : 1;
|
||||||
|
|
||||||
const tonConfigPermille = global.appConfig.tonStargiftResaleCommissionPermille;
|
const tonCommission = selectTonGiftResaleCommission(global);
|
||||||
const tonStargiftResaleCommissionPermille = tonConfigPermille ? (tonConfigPermille / 1000) : 0;
|
const tonResaleAmountMin = convertTonFromNanos(global.appConfig.tonStargiftResaleAmountMin || 0);
|
||||||
const tonStargiftResaleAmountMin = convertTonFromNanos(global.appConfig.tonStargiftResaleAmountMin || 0);
|
|
||||||
const maxTonFromConfig = global.appConfig.tonStargiftResaleAmountMax;
|
const maxTonFromConfig = global.appConfig.tonStargiftResaleAmountMax;
|
||||||
const tonStargiftResaleAmountMax = maxTonFromConfig && convertTonFromNanos(maxTonFromConfig);
|
const tonResaleAmountMax = maxTonFromConfig ? convertTonFromNanos(maxTonFromConfig) : undefined;
|
||||||
|
|
||||||
const tonUsdRate = global.appConfig.tonUsdRate;
|
const tonUsdRate = global.appConfig.tonUsdRate;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
starsStargiftResaleCommissionPermille,
|
starsCommission,
|
||||||
starsStargiftResaleAmountMin,
|
starsResaleAmountMin,
|
||||||
starsStargiftResaleAmountMax,
|
starsResaleAmountMax,
|
||||||
starsUsdWithdrawRate,
|
starsUsdWithdrawRate,
|
||||||
tonStargiftResaleCommissionPermille,
|
tonCommission,
|
||||||
tonStargiftResaleAmountMin,
|
tonResaleAmountMin,
|
||||||
tonStargiftResaleAmountMax,
|
tonResaleAmountMax,
|
||||||
tonUsdRate,
|
tonUsdRate,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -709,3 +709,29 @@ addActionHandler('openGiftAuctionAcquiredModal', async (global, actions, payload
|
|||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('acceptStarGiftOffer', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { messageId } = payload;
|
||||||
|
|
||||||
|
const result = await callApi('resolveStarGiftOffer', {
|
||||||
|
offerMsgId: messageId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.loadStarStatus();
|
||||||
|
if (global.currentUserId) {
|
||||||
|
actions.reloadPeerSavedGifts({ peerId: global.currentUserId });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('declineStarGiftOffer', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { messageId } = payload;
|
||||||
|
|
||||||
|
await callApi('resolveStarGiftOffer', {
|
||||||
|
offerMsgId: messageId,
|
||||||
|
shouldDecline: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -620,6 +620,23 @@ addActionHandler('openGiftDescriptionRemoveModal', (global, actions, payload): A
|
|||||||
|
|
||||||
addTabStateResetterAction('closeGiftDescriptionRemoveModal', 'giftDescriptionRemoveModal');
|
addTabStateResetterAction('closeGiftDescriptionRemoveModal', 'giftDescriptionRemoveModal');
|
||||||
|
|
||||||
|
addActionHandler('openGiftOfferAcceptModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const {
|
||||||
|
peerId, messageId, gift, price, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
giftOfferAcceptModal: {
|
||||||
|
peerId,
|
||||||
|
messageId,
|
||||||
|
gift,
|
||||||
|
price,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addTabStateResetterAction('closeGiftOfferAcceptModal', 'giftOfferAcceptModal');
|
||||||
|
|
||||||
addActionHandler('updateSelectedGiftCollection', (global, actions, payload): ActionReturnType => {
|
addActionHandler('updateSelectedGiftCollection', (global, actions, payload): ActionReturnType => {
|
||||||
const { peerId, collectionId, tabId = getCurrentTabId() } = payload;
|
const { peerId, collectionId, tabId = getCurrentTabId() } = payload;
|
||||||
const tabState = selectTabState(global, tabId);
|
const tabState = selectTabState(global, tabId);
|
||||||
|
|||||||
@ -106,3 +106,12 @@ export function selectActiveGiftsCollectionId<T extends GlobalState>(
|
|||||||
): ProfileCollectionKey {
|
): ProfileCollectionKey {
|
||||||
return selectTabState(global, tabId).savedGifts.activeCollectionByPeerId?.[peerId] || 'all';
|
return selectTabState(global, tabId).savedGifts.activeCollectionByPeerId?.[peerId] || 'all';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectStarsGiftResaleCommission<T extends GlobalState>(global: T) {
|
||||||
|
const permille = global.appConfig?.starsStargiftResaleCommissionPermille;
|
||||||
|
return permille !== undefined ? permille / 1000 : undefined;
|
||||||
|
}
|
||||||
|
export function selectTonGiftResaleCommission<T extends GlobalState>(global: T) {
|
||||||
|
const permille = global.appConfig?.tonStargiftResaleCommissionPermille;
|
||||||
|
return permille !== undefined ? permille / 1000 : undefined;
|
||||||
|
}
|
||||||
|
|||||||
@ -2768,6 +2768,13 @@ export interface ActionPayloads {
|
|||||||
details: ApiStarGiftAttributeOriginalDetails;
|
details: ApiStarGiftAttributeOriginalDetails;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
closeGiftDescriptionRemoveModal: WithTabId | undefined;
|
closeGiftDescriptionRemoveModal: WithTabId | undefined;
|
||||||
|
openGiftOfferAcceptModal: {
|
||||||
|
peerId: string;
|
||||||
|
messageId: number;
|
||||||
|
gift: ApiStarGiftUnique;
|
||||||
|
price: ApiTypeCurrencyAmount;
|
||||||
|
} & WithTabId;
|
||||||
|
closeGiftOfferAcceptModal: WithTabId | undefined;
|
||||||
updateSelectedGiftCollection: {
|
updateSelectedGiftCollection: {
|
||||||
peerId: string;
|
peerId: string;
|
||||||
collectionId: number;
|
collectionId: number;
|
||||||
@ -2805,6 +2812,13 @@ export interface ActionPayloads {
|
|||||||
hash?: string;
|
hash?: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|
||||||
|
acceptStarGiftOffer: {
|
||||||
|
messageId: number;
|
||||||
|
} & WithTabId;
|
||||||
|
declineStarGiftOffer: {
|
||||||
|
messageId: number;
|
||||||
|
} & WithTabId;
|
||||||
|
|
||||||
openStarsGiftModal: ({
|
openStarsGiftModal: ({
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
forUserId?: string;
|
forUserId?: string;
|
||||||
|
|||||||
@ -871,6 +871,13 @@ export type TabState = {
|
|||||||
details: ApiStarGiftAttributeOriginalDetails;
|
details: ApiStarGiftAttributeOriginalDetails;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
giftOfferAcceptModal?: {
|
||||||
|
peerId: string;
|
||||||
|
messageId: number;
|
||||||
|
gift: ApiStarGiftUnique;
|
||||||
|
price: ApiTypeCurrencyAmount;
|
||||||
|
};
|
||||||
|
|
||||||
giftUpgradeModal?: {
|
giftUpgradeModal?: {
|
||||||
sampleAttributes: ApiStarGiftAttribute[];
|
sampleAttributes: ApiStarGiftAttribute[];
|
||||||
recipientId?: string;
|
recipientId?: string;
|
||||||
|
|||||||
@ -1883,6 +1883,7 @@ payments.getUniqueStarGiftValueInfo#4365af6b slug:string = payments.UniqueStarGi
|
|||||||
payments.checkCanSendGift#c0c4edc9 gift_id:long = payments.CheckCanSendGiftResult;
|
payments.checkCanSendGift#c0c4edc9 gift_id:long = payments.CheckCanSendGiftResult;
|
||||||
payments.getStarGiftAuctionState#5c9ff4d6 auction:InputStarGiftAuction version:int = payments.StarGiftAuctionState;
|
payments.getStarGiftAuctionState#5c9ff4d6 auction:InputStarGiftAuction version:int = payments.StarGiftAuctionState;
|
||||||
payments.getStarGiftAuctionAcquiredGifts#6ba2cbec gift_id:long = payments.StarGiftAuctionAcquiredGifts;
|
payments.getStarGiftAuctionAcquiredGifts#6ba2cbec gift_id:long = payments.StarGiftAuctionAcquiredGifts;
|
||||||
|
payments.resolveStarGiftOffer#e9ce781c flags:# decline:flags.0?true offer_msg_id:int = Updates;
|
||||||
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||||
phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||||
phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
|
phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||||
|
|||||||
@ -354,6 +354,7 @@
|
|||||||
"payments.getStarGiftCollections",
|
"payments.getStarGiftCollections",
|
||||||
"payments.getStarGiftAuctionState",
|
"payments.getStarGiftAuctionState",
|
||||||
"payments.getStarGiftAuctionAcquiredGifts",
|
"payments.getStarGiftAuctionAcquiredGifts",
|
||||||
|
"payments.resolveStarGiftOffer",
|
||||||
"langpack.getLangPack",
|
"langpack.getLangPack",
|
||||||
"langpack.getStrings",
|
"langpack.getStrings",
|
||||||
"langpack.getLanguages",
|
"langpack.getLanguages",
|
||||||
|
|||||||
65
src/types/language.d.ts
vendored
65
src/types/language.d.ts
vendored
@ -1525,6 +1525,13 @@ export interface LangPair {
|
|||||||
'ActionStarGiftUniqueBackdrop': undefined;
|
'ActionStarGiftUniqueBackdrop': undefined;
|
||||||
'ActionStarGiftUniqueSymbol': undefined;
|
'ActionStarGiftUniqueSymbol': undefined;
|
||||||
'ActionStarGiftSelf': undefined;
|
'ActionStarGiftSelf': undefined;
|
||||||
|
'ActionStarGiftOfferAccepted': undefined;
|
||||||
|
'ActionStarGiftOfferRejected': undefined;
|
||||||
|
'ActionStarGiftOfferHasExpired': undefined;
|
||||||
|
'GiftOfferReject': undefined;
|
||||||
|
'GiftOfferAccept': undefined;
|
||||||
|
'GiftOfferRejectTitle': undefined;
|
||||||
|
'GiftOfferAcceptTitle': undefined;
|
||||||
'ActionSuggestedPhotoButton': undefined;
|
'ActionSuggestedPhotoButton': undefined;
|
||||||
'ActionSuggestedVideoTitle': undefined;
|
'ActionSuggestedVideoTitle': undefined;
|
||||||
'ActionSuggestedVideoText': undefined;
|
'ActionSuggestedVideoText': undefined;
|
||||||
@ -2271,6 +2278,9 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'GiftInfoPeerDescriptionFreeUpgradeOut': {
|
'GiftInfoPeerDescriptionFreeUpgradeOut': {
|
||||||
'peer': V;
|
'peer': V;
|
||||||
};
|
};
|
||||||
|
'GiftInfoValueAmount': {
|
||||||
|
'amount': V;
|
||||||
|
};
|
||||||
'GiftInfoPeerConvertDescription': {
|
'GiftInfoPeerConvertDescription': {
|
||||||
'peer': V;
|
'peer': V;
|
||||||
'amount': V;
|
'amount': V;
|
||||||
@ -2655,6 +2665,16 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'ActionStarGiftTransferredUnknownChannel': {
|
'ActionStarGiftTransferredUnknownChannel': {
|
||||||
'channel': V;
|
'channel': V;
|
||||||
};
|
};
|
||||||
|
'ActionStarGiftSoldFromOffer': {
|
||||||
|
'gift': V;
|
||||||
|
'user': V;
|
||||||
|
'cost': V;
|
||||||
|
};
|
||||||
|
'ActionStarGiftBoughtFromOffer': {
|
||||||
|
'user': V;
|
||||||
|
'gift': V;
|
||||||
|
'cost': V;
|
||||||
|
};
|
||||||
'ActionStarGiftReceivedAnonymous': {
|
'ActionStarGiftReceivedAnonymous': {
|
||||||
'cost': V;
|
'cost': V;
|
||||||
};
|
};
|
||||||
@ -2719,6 +2739,51 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'ActionStarGiftAuctionBought': {
|
'ActionStarGiftAuctionBought': {
|
||||||
'cost': V;
|
'cost': V;
|
||||||
};
|
};
|
||||||
|
'ActionStarGiftOfferOutgoing': {
|
||||||
|
'peer': V;
|
||||||
|
'cost': V;
|
||||||
|
'gift': V;
|
||||||
|
};
|
||||||
|
'ActionStarGiftOfferIncoming': {
|
||||||
|
'peer': V;
|
||||||
|
'cost': V;
|
||||||
|
'gift': V;
|
||||||
|
};
|
||||||
|
'ActionStarGiftOfferExpires': {
|
||||||
|
'time': V;
|
||||||
|
};
|
||||||
|
'ActionStarGiftOfferDeclinedOutgoing': {
|
||||||
|
'peer': V;
|
||||||
|
'gift': V;
|
||||||
|
'cost': V;
|
||||||
|
};
|
||||||
|
'ActionStarGiftOfferDeclinedIncoming': {
|
||||||
|
'peer': V;
|
||||||
|
'gift': V;
|
||||||
|
'cost': V;
|
||||||
|
};
|
||||||
|
'GiftOfferRejectText': {
|
||||||
|
'user': V;
|
||||||
|
};
|
||||||
|
'GiftOfferAcceptText': {
|
||||||
|
'gift': V;
|
||||||
|
'user': V;
|
||||||
|
'price': V;
|
||||||
|
};
|
||||||
|
'GiftOfferAcceptReceive': {
|
||||||
|
'amount': V;
|
||||||
|
};
|
||||||
|
'GiftOfferAcceptButton': {
|
||||||
|
'amount': V;
|
||||||
|
};
|
||||||
|
'GiftOfferPriceLow': {
|
||||||
|
'percent': V;
|
||||||
|
'gift': V;
|
||||||
|
};
|
||||||
|
'GiftOfferPriceHigh': {
|
||||||
|
'percent': V;
|
||||||
|
'gift': V;
|
||||||
|
};
|
||||||
'ActionSuggestedPhotoYou': {
|
'ActionSuggestedPhotoYou': {
|
||||||
'user': V;
|
'user': V;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -339,6 +339,26 @@ export function formatMediaDuration(duration: number, maxValue?: number) {
|
|||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatShortHoursMinutes(lang: OldLangFn, durationInSeconds: number) {
|
||||||
|
if (durationInSeconds <= 0) {
|
||||||
|
return lang('MessageTimer.ShortMinutes', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hours = Math.floor(durationInSeconds / 3600);
|
||||||
|
const minutes = Math.floor((durationInSeconds % 3600) / 60);
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
const hoursText = lang('MessageTimer.ShortHours', hours);
|
||||||
|
if (minutes === 0) {
|
||||||
|
return hoursText;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minutesText = lang('MessageTimer.ShortMinutes', minutes);
|
||||||
|
return `${hoursText} ${minutesText}`;
|
||||||
|
}
|
||||||
|
return lang('MessageTimer.ShortMinutes', minutes);
|
||||||
|
}
|
||||||
|
|
||||||
export function formatVoiceRecordDuration(durationInMs: number) {
|
export function formatVoiceRecordDuration(durationInMs: number) {
|
||||||
const parts = [];
|
const parts = [];
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
|
import type { ApiTypeCurrencyAmount } from '../../api/types';
|
||||||
import type { LangFn } from './types';
|
import type { LangFn } from './types';
|
||||||
|
|
||||||
import { STARS_ICON_PLACEHOLDER } from '../../config';
|
import { STARS_ICON_PLACEHOLDER, TON_CURRENCY_CODE } from '../../config';
|
||||||
import { convertTonFromNanos } from '../../util/formatCurrency';
|
import { convertTonFromNanos } from '../../util/formatCurrency';
|
||||||
import buildClassName from '../buildClassName';
|
import buildClassName from '../buildClassName';
|
||||||
|
|
||||||
@ -74,3 +75,12 @@ export function formatStarsAsIcon(lang: LangFn, amount: number | string, options
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatCurrencyAmountAsText(lang: LangFn, currencyAmount: ApiTypeCurrencyAmount) {
|
||||||
|
if (currencyAmount.currency === TON_CURRENCY_CODE) {
|
||||||
|
return formatTonAsText(lang, currencyAmount.amount, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = currencyAmount.amount + currencyAmount.nanos / 1e9;
|
||||||
|
return formatStarsAsText(lang, amount);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user