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;
|
||||
stars_paid_messages_available?: boolean;
|
||||
stars_usd_withdraw_rate_x1000?: number;
|
||||
stars_usd_sell_rate_x1000?: number;
|
||||
stars_paid_message_commission_permille?: number;
|
||||
stars_paid_message_amount_max?: 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,
|
||||
starsPaidMessageAmountMax: appConfig.stars_paid_message_amount_max,
|
||||
starsUsdWithdrawRateX1000: appConfig.stars_usd_withdraw_rate_x1000,
|
||||
starsUsdSellRateX1000: appConfig.stars_usd_sell_rate_x1000,
|
||||
bandwidthPremiumNotifyPeriod: appConfig.upload_premium_speedup_notify_period,
|
||||
bandwidthPremiumUploadSpeedup: appConfig.upload_premium_speedup_upload,
|
||||
bandwidthPremiumDownloadSpeedup: appConfig.upload_premium_speedup_download,
|
||||
|
||||
@ -35,6 +35,7 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
||||
const {
|
||||
id, num, ownerId, ownerName, title, attributes, availabilityIssued, availabilityTotal, slug, ownerAddress,
|
||||
giftAddress, resellAmount, releasedBy, resaleTonOnly, requirePremium, valueCurrency, valueAmount, giftId,
|
||||
valueUsdAmount,
|
||||
} = starGift;
|
||||
|
||||
return {
|
||||
@ -56,7 +57,9 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
||||
resaleTonOnly,
|
||||
valueCurrency,
|
||||
valueAmount: toJSNumber(valueAmount),
|
||||
valueUsdAmount: toJSNumber(valueUsdAmount),
|
||||
regularGiftId: giftId.toString(),
|
||||
offerMinStars: starGift.offerMinStars,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -426,7 +426,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
||||
if (action instanceof GramJs.MessageActionStarGiftUnique) {
|
||||
const {
|
||||
upgrade, transferred, saved, refunded, gift, canExportAt, transferStars, fromId, peer, savedId,
|
||||
resaleAmount, prepaidUpgrade, dropOriginalDetailsStars,
|
||||
resaleAmount, prepaidUpgrade, dropOriginalDetailsStars, fromOffer,
|
||||
} = action;
|
||||
|
||||
const starGift = buildApiStarGift(gift);
|
||||
@ -440,6 +440,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
||||
isSaved: saved,
|
||||
isRefunded: refunded,
|
||||
isPrepaidUpgrade: prepaidUpgrade,
|
||||
isFromOffer: fromOffer,
|
||||
gift: starGift,
|
||||
canExportAt,
|
||||
transferStars: toJSNumber(transferStars),
|
||||
@ -523,6 +524,38 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
||||
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;
|
||||
}
|
||||
|
||||
@ -590,3 +590,18 @@ export async function fetchStarGiftCollections({
|
||||
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;
|
||||
isRefunded?: true;
|
||||
isPrepaidUpgrade?: true;
|
||||
isFromOffer?: true;
|
||||
gift: ApiStarGiftUnique;
|
||||
canExportAt?: number;
|
||||
transferStars?: number;
|
||||
@ -329,6 +330,22 @@ export interface ApiMessageActionTodoAppendTasks extends ActionMediaType {
|
||||
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 {
|
||||
type: 'unsupported';
|
||||
}
|
||||
@ -348,4 +365,5 @@ export type ApiMessageAction = ApiMessageActionUnsupported | ApiMessageActionCha
|
||||
| ApiMessageActionGiftTon | ApiMessageActionPrizeStars | ApiMessageActionStarGift | ApiMessageActionStarGiftUnique
|
||||
| ApiMessageActionPaidMessagesRefunded | ApiMessageActionPaidMessagesPrice | ApiMessageActionSuggestedPostApproval
|
||||
| ApiMessageActionSuggestedPostSuccess | ApiMessageActionSuggestedPostRefund | ApiMessageActionTodoCompletions
|
||||
| ApiMessageActionTodoAppendTasks;
|
||||
| ApiMessageActionTodoAppendTasks | ApiMessageActionStarGiftPurchaseOffer
|
||||
| ApiMessageActionStarGiftPurchaseOfferDeclined;
|
||||
|
||||
@ -943,6 +943,12 @@ export interface KeyboardButtonOpenThread {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface KeyboardButtonGiftOffer {
|
||||
type: 'giftOffer';
|
||||
text: string;
|
||||
buttonType: 'accept' | 'reject';
|
||||
}
|
||||
|
||||
export type ApiKeyboardButton = (
|
||||
ApiKeyboardButtonSimple
|
||||
| ApiKeyboardButtonReceipt
|
||||
@ -957,6 +963,7 @@ export type ApiKeyboardButton = (
|
||||
| ApiKeyboardButtonCopy
|
||||
| KeyboardButtonSuggestedMessage
|
||||
| KeyboardButtonOpenThread
|
||||
| KeyboardButtonGiftOffer
|
||||
);
|
||||
|
||||
export type ApiKeyboardButtons = ApiKeyboardButton[][];
|
||||
|
||||
@ -240,6 +240,7 @@ export interface ApiAppConfig {
|
||||
starsPaidMessageCommissionPermille?: number;
|
||||
starsPaidMessageAmountMax?: number;
|
||||
starsUsdWithdrawRateX1000: number;
|
||||
starsUsdSellRateX1000?: number;
|
||||
bandwidthPremiumNotifyPeriod?: number;
|
||||
bandwidthPremiumUploadSpeedup?: number;
|
||||
bandwidthPremiumDownloadSpeedup?: number;
|
||||
|
||||
@ -59,6 +59,8 @@ export interface ApiStarGiftUnique {
|
||||
resaleTonOnly?: true;
|
||||
valueCurrency?: string;
|
||||
valueAmount?: number;
|
||||
valueUsdAmount?: number;
|
||||
offerMinStars?: number;
|
||||
}
|
||||
|
||||
export type ApiStarGift = ApiStarGiftRegular | ApiStarGiftUnique;
|
||||
|
||||
@ -1526,6 +1526,7 @@
|
||||
"GiftInfoFrom" = "From";
|
||||
"GiftInfoDate" = "Date";
|
||||
"GiftInfoValue" = "Value";
|
||||
"GiftInfoValueAmount" = "~ {amount}";
|
||||
"GiftInfoConvert_one" = "Convert to {amount} Star";
|
||||
"GiftInfoConvert_other" = "Convert to {amount} Stars";
|
||||
"GiftInfoConvertTitle" = "Convert Gift to Stars";
|
||||
@ -1912,6 +1913,8 @@
|
||||
"ActionStarGiftTransferredSelf" = "You transferred a unique collectible";
|
||||
"ActionStarGiftTransferredUnknown" = "Someone transferred you a gift";
|
||||
"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}";
|
||||
"ActionStarGiftSentChannel" = "{user} 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}";
|
||||
"ActionStarGiftAuctionFor" = "Gift for {peer}";
|
||||
"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.";
|
||||
"ActionSuggestedPhoto" = "{user} suggests this photo for your Telegram profile.";
|
||||
"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 GiftTransferConfirmModal } from '../components/modals/gift/transfer/GiftTransferConfirmModal';
|
||||
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 PriceConfirmModal } from '../components/modals/priceConfirm/PriceConfirmModal';
|
||||
|
||||
@ -82,6 +82,16 @@
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.contentWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
:global(.InlineButtons) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.contextContainer {
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
@ -303,3 +313,7 @@
|
||||
font-size: 1.5rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.narrowWrapper {
|
||||
max-width: 18rem;
|
||||
}
|
||||
|
||||
@ -10,11 +10,18 @@ import type {
|
||||
ThreadId,
|
||||
} from '../../../types';
|
||||
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 { MESSAGE_APPEARANCE_DELAY } from '../../../config';
|
||||
import { getMessageHtmlId } from '../../../global/helpers';
|
||||
import { getPeerTitle } from '../../../global/helpers/peers';
|
||||
import { getMessageReplyInfo } from '../../../global/helpers/replies';
|
||||
import {
|
||||
selectActionMessageBg,
|
||||
@ -31,6 +38,7 @@ import { IS_TAURI } from '../../../util/browser/globalEnvironment';
|
||||
import { IS_ANDROID, IS_FLUID_BACKGROUND_SUPPORTED } from '../../../util/browser/windowEnvironment';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { isLocalMessageId } from '../../../util/keys/messageKey';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { isElementInViewport } from '../../../util/visibility/isElementInViewport';
|
||||
import { preventMessageInputBlur } from '../helpers/preventMessageInputBlur';
|
||||
|
||||
@ -39,23 +47,27 @@ import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||
import useEnsureMessage from '../../../hooks/useEnsureMessage';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useShowTransition from '../../../hooks/useShowTransition';
|
||||
import { type OnIntersectPinnedMessage } from '../hooks/usePinnedMessage';
|
||||
import useFluidBackgroundFilter from './hooks/useFluidBackgroundFilter';
|
||||
import useFocusMessageListElement from './hooks/useFocusMessageListElement';
|
||||
|
||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||
import ActionMessageText from './ActionMessageText';
|
||||
import ChannelPhoto from './actions/ChannelPhoto';
|
||||
import Gift from './actions/Gift';
|
||||
import GiveawayPrize from './actions/GiveawayPrize';
|
||||
import StarGift from './actions/StarGift';
|
||||
import StarGiftPurchaseOffer from './actions/StarGiftPurchaseOffer';
|
||||
import StarGiftUnique from './actions/StarGiftUnique';
|
||||
import SuggestedPhoto from './actions/SuggestedPhoto';
|
||||
import SuggestedPostApproval from './actions/SuggestedPostApproval';
|
||||
import SuggestedPostBalanceTooLow from './actions/SuggestedPostBalanceTooLow';
|
||||
import SuggestedPostRejected from './actions/SuggestedPostRejected';
|
||||
import ContextMenuContainer from './ContextMenuContainer';
|
||||
import InlineButtons from './InlineButtons';
|
||||
import Reactions from './reactions/Reactions';
|
||||
import SimilarChannels from './SimilarChannels';
|
||||
|
||||
@ -101,7 +113,7 @@ const SINGLE_LINE_ACTIONS = new Set<ApiMessageAction['type']>([
|
||||
'unsupported',
|
||||
]);
|
||||
const HIDDEN_TEXT_ACTIONS = new Set<ApiMessageAction['type']>(['giftCode', 'prizeStars',
|
||||
'suggestProfilePhoto', 'suggestedPostApproval']);
|
||||
'suggestProfilePhoto', 'suggestedPostApproval', 'starGiftPurchaseOffer']);
|
||||
|
||||
const ActionMessage = ({
|
||||
message,
|
||||
@ -143,6 +155,8 @@ const ActionMessage = ({
|
||||
animateUnreadReaction,
|
||||
markMentionsRead,
|
||||
focusMessage,
|
||||
openGiftOfferAcceptModal,
|
||||
declineStarGiftOffer,
|
||||
} = getActions();
|
||||
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
@ -155,16 +169,67 @@ const ActionMessage = ({
|
||||
const isSingleLine = SINGLE_LINE_ACTIONS.has(action.type);
|
||||
const isFluidMultiline = IS_FLUID_BACKGROUND_SUPPORTED && !isSingleLine;
|
||||
const isClickableText = action.type === 'suggestedPostSuccess';
|
||||
const isNarrowMessage = action.type === 'starGiftPurchaseOfferDeclined';
|
||||
|
||||
const messageReplyInfo = getMessageReplyInfo(message);
|
||||
const { replyToMsgId, replyToPeerId } = messageReplyInfo || {};
|
||||
|
||||
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 lang = useLang();
|
||||
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);
|
||||
|
||||
useEnsureMessage(
|
||||
@ -247,6 +312,13 @@ const ActionMessage = ({
|
||||
|
||||
const fluidBackgroundStyle = useFluidBackgroundFilter(isFluidMultiline ? actionMessageBg : undefined);
|
||||
|
||||
const handleKeyDown = useLastCallback((e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
handleClick();
|
||||
}
|
||||
});
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
switch (action.type) {
|
||||
case 'paymentSent':
|
||||
@ -309,6 +381,18 @@ const ActionMessage = ({
|
||||
break;
|
||||
}
|
||||
|
||||
case 'starGiftPurchaseOffer': {
|
||||
if (shouldRenderGiftOfferButtons) {
|
||||
openGiftOfferAcceptModal({
|
||||
peerId: chatId,
|
||||
messageId: id,
|
||||
gift: action.gift,
|
||||
price: action.price,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'channelJoined': {
|
||||
toggleChannelRecommendations({ chatId });
|
||||
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':
|
||||
return (
|
||||
<SimilarChannels
|
||||
@ -442,7 +538,9 @@ const ActionMessage = ({
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}, [action, message, observeIntersectionForLoading, sender, observeIntersectionForPlaying]);
|
||||
}, [
|
||||
action, message, observeIntersectionForLoading, sender, observeIntersectionForPlaying, shouldRenderGiftOfferButtons,
|
||||
]);
|
||||
|
||||
if ((isInsideTopic && action.type === 'topicCreate') || action.type === 'phoneCall') {
|
||||
return undefined;
|
||||
@ -473,20 +571,46 @@ const ActionMessage = ({
|
||||
{!isTextHidden && (
|
||||
<>
|
||||
{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}>
|
||||
<ActionMessageText message={message} isInsideTopic={isInsideTopic} />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={buildClassName(styles.inlineWrapper, isClickableText && styles.hoverable)}>
|
||||
<span className={styles.textContent} onClick={handleClick}>
|
||||
<div className={buildClassName(
|
||||
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} />
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{fullContent}
|
||||
{(fullContent || shouldRenderInlineButtons) && (
|
||||
<div className={styles.contentWrapper}>
|
||||
{fullContent}
|
||||
{shouldRenderInlineButtons && (
|
||||
<InlineButtons
|
||||
inlineButtons={giftOfferInlineButtons}
|
||||
onClick={handleInlineButtonClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{contextMenuAnchor && (
|
||||
<ContextMenuContainer
|
||||
isOpen={isContextMenuOpen}
|
||||
@ -508,6 +632,19 @@ const ActionMessage = ({
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -30,7 +30,7 @@ import { ensureProtocol } from '../../../util/browser/url';
|
||||
import { formatDateTimeToString, formatScheduledDateTime, formatShortDuration } from '../../../util/dates/dateFormat';
|
||||
import { formatCurrency } 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 { getServerTime } from '../../../util/serverTime';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
@ -616,6 +616,7 @@ const ActionMessageText = ({
|
||||
case 'starGiftUnique': {
|
||||
const {
|
||||
isTransferred, isUpgrade, savedId, peerId, fromId, resaleAmount, gift, transferStars, isPrepaidUpgrade,
|
||||
isFromOffer,
|
||||
} = action;
|
||||
|
||||
const isToChannel = Boolean(peerId && savedId);
|
||||
@ -629,6 +630,28 @@ const ActionMessageText = ({
|
||||
|| (isToChannel ? channelFallbackText : userFallbackText);
|
||||
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 (isOutgoing) {
|
||||
return lang('ActionStarGiftPrepaidUpgradedYou');
|
||||
@ -637,9 +660,7 @@ const ActionMessageText = ({
|
||||
}
|
||||
|
||||
if (resaleAmount && !transferStars) {
|
||||
const amountText = resaleAmount.currency === TON_CURRENCY_CODE
|
||||
? formatTonAsText(lang, convertTonFromNanos(resaleAmount.amount))
|
||||
: formatStarsAsText(lang, resaleAmount.amount);
|
||||
const amountText = formatCurrencyAmountAsText(lang, resaleAmount);
|
||||
|
||||
return lang(
|
||||
isOutgoing
|
||||
@ -1023,6 +1044,55 @@ const ActionMessageText = ({
|
||||
|
||||
case 'phoneCall': // Rendered as a regular message, but considered an action for the summary
|
||||
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:
|
||||
return lang(UNSUPPORTED_LANG_KEY);
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
|
||||
transition: background-color 150ms, color 150ms, backdrop-filter 150ms, filter 150ms;
|
||||
|
||||
&:active,
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: var(--action-message-bg) !important;
|
||||
@ -61,6 +62,7 @@
|
||||
|
||||
.left-icon {
|
||||
margin-right: 0.25rem;
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
.inline-button-text {
|
||||
|
||||
@ -58,6 +58,14 @@ const InlineButtons = ({ inlineButtons, onClick }: OwnProps) => {
|
||||
return <Icon className="left-icon" name="close" />;
|
||||
}
|
||||
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;
|
||||
|
||||
@ -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 GiftLockedModal from './gift/locked/GiftLockedModal.async';
|
||||
import GiftDescriptionRemoveModal from './gift/message/GiftDescriptionRemoveModal.async';
|
||||
import GiftOfferAcceptModal from './gift/offer/GiftOfferAcceptModal.async';
|
||||
import GiftRecipientPicker from './gift/recipient/GiftRecipientPicker.async';
|
||||
import GiftResalePriceComposerModal from './gift/resale/GiftResalePriceComposerModal.async';
|
||||
import StarGiftPriceDecreaseInfoModal from './gift/StarGiftPriceDecreaseInfoModal.async';
|
||||
@ -115,6 +116,7 @@ type ModalKey = keyof Pick<TabState,
|
||||
'giftTransferModal' |
|
||||
'giftTransferConfirmModal' |
|
||||
'giftDescriptionRemoveModal' |
|
||||
'giftOfferAcceptModal' |
|
||||
'chatRefundModal' |
|
||||
'priceConfirmModal' |
|
||||
'isFrozenAccountModalOpen' |
|
||||
@ -188,6 +190,7 @@ const MODALS: ModalRegistry = {
|
||||
giftTransferModal: GiftTransferModal,
|
||||
giftTransferConfirmModal: GiftTransferConfirmModal,
|
||||
giftDescriptionRemoveModal: GiftDescriptionRemoveModal,
|
||||
giftOfferAcceptModal: GiftOfferAcceptModal,
|
||||
chatRefundModal: ChatRefundModal,
|
||||
priceConfirmModal: PriceConfirmModal,
|
||||
isFrozenAccountModalOpen: FrozenAccountModal,
|
||||
|
||||
@ -743,16 +743,11 @@ const GiftInfoModal = ({
|
||||
]);
|
||||
|
||||
if (gift.valueAmount && gift.valueCurrency) {
|
||||
const formattedValue = formatCurrencyAsString(gift.valueAmount, gift.valueCurrency, lang.code);
|
||||
tableData.push([
|
||||
lang('GiftInfoValue'),
|
||||
<span className={styles.uniqueValue}>
|
||||
~
|
||||
{' '}
|
||||
{formatCurrencyAsString(
|
||||
gift.valueAmount,
|
||||
gift.valueCurrency,
|
||||
lang.code,
|
||||
)}
|
||||
{lang('GiftInfoValueAmount', { amount: formattedValue })}
|
||||
<BadgeButton onClick={handleOpenValueModal}>
|
||||
{lang('GiftInfoValueLinkMore')}
|
||||
</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 {
|
||||
selectStarsGiftResaleCommission,
|
||||
selectTonGiftResaleCommission,
|
||||
} from '../../../../global/selectors';
|
||||
import { convertTonFromNanos, convertTonToNanos } from '../../../../util/formatCurrency';
|
||||
import { convertTonToUsd, formatCurrencyAsString } from '../../../../util/formatCurrency';
|
||||
import { formatStarsAsIcon, formatStarsAsText, formatTonAsIcon,
|
||||
@ -26,20 +30,20 @@ export type OwnProps = {
|
||||
};
|
||||
|
||||
export type StateProps = {
|
||||
starsStargiftResaleCommissionPermille?: number;
|
||||
starsStargiftResaleAmountMin: number;
|
||||
starsStargiftResaleAmountMax?: number;
|
||||
starsCommission?: number;
|
||||
starsResaleAmountMin: number;
|
||||
starsResaleAmountMax?: number;
|
||||
starsUsdWithdrawRate?: number;
|
||||
tonStargiftResaleCommissionPermille?: number;
|
||||
tonStargiftResaleAmountMin: number;
|
||||
tonStargiftResaleAmountMax?: number;
|
||||
tonCommission?: number;
|
||||
tonResaleAmountMin: number;
|
||||
tonResaleAmountMax?: number;
|
||||
tonUsdRate?: number;
|
||||
};
|
||||
|
||||
const GiftResalePriceComposerModal = ({
|
||||
modal, starsStargiftResaleCommissionPermille,
|
||||
starsStargiftResaleAmountMin, starsStargiftResaleAmountMax, starsUsdWithdrawRate,
|
||||
tonStargiftResaleCommissionPermille, tonStargiftResaleAmountMin, tonStargiftResaleAmountMax, tonUsdRate,
|
||||
modal, starsCommission,
|
||||
starsResaleAmountMin, starsResaleAmountMax, starsUsdWithdrawRate,
|
||||
tonCommission, tonResaleAmountMin, tonResaleAmountMax, tonUsdRate,
|
||||
}: OwnProps & StateProps) => {
|
||||
const {
|
||||
closeGiftResalePriceComposerModal,
|
||||
@ -62,7 +66,7 @@ const GiftResalePriceComposerModal = ({
|
||||
const handleChangePrice = useLastCallback((e) => {
|
||||
const value = e.target.value;
|
||||
const number = parseFloat(value);
|
||||
const maxAmount = isPriceInTon ? tonStargiftResaleAmountMax : starsStargiftResaleAmountMax;
|
||||
const maxAmount = isPriceInTon ? tonResaleAmountMax : starsResaleAmountMax;
|
||||
const result = value === '' || Number.isNaN(number) ? undefined
|
||||
: maxAmount ? Math.min(number, maxAmount) : number;
|
||||
setPrice(result);
|
||||
@ -95,8 +99,8 @@ const GiftResalePriceComposerModal = ({
|
||||
},
|
||||
});
|
||||
});
|
||||
const commission = isPriceInTon ? tonStargiftResaleCommissionPermille : starsStargiftResaleCommissionPermille;
|
||||
const minAmount = isPriceInTon ? tonStargiftResaleAmountMin : starsStargiftResaleAmountMin;
|
||||
const commission = isPriceInTon ? tonCommission : starsCommission;
|
||||
const minAmount = isPriceInTon ? tonResaleAmountMin : starsResaleAmountMin;
|
||||
const isPriceCorrect = hasPrice && price >= minAmount;
|
||||
|
||||
return (
|
||||
@ -176,30 +180,28 @@ const GiftResalePriceComposerModal = ({
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): Complete<StateProps> => {
|
||||
const configPermille = global.appConfig.starsStargiftResaleCommissionPermille;
|
||||
const starsStargiftResaleCommissionPermille = configPermille ? (configPermille / 1000) : undefined;
|
||||
const starsStargiftResaleAmountMin = global.appConfig.starsStargiftResaleAmountMin || 0;
|
||||
const starsStargiftResaleAmountMax = global.appConfig.starsStargiftResaleAmountMax;
|
||||
const starsCommission = selectStarsGiftResaleCommission(global);
|
||||
const starsResaleAmountMin = global.appConfig.starsStargiftResaleAmountMin || 0;
|
||||
const starsResaleAmountMax = global.appConfig.starsStargiftResaleAmountMax;
|
||||
|
||||
const starsUsdWithdrawRateX1000 = global.appConfig.starsUsdWithdrawRateX1000;
|
||||
const starsUsdWithdrawRate = starsUsdWithdrawRateX1000 ? starsUsdWithdrawRateX1000 / 1000 : 1;
|
||||
|
||||
const tonConfigPermille = global.appConfig.tonStargiftResaleCommissionPermille;
|
||||
const tonStargiftResaleCommissionPermille = tonConfigPermille ? (tonConfigPermille / 1000) : 0;
|
||||
const tonStargiftResaleAmountMin = convertTonFromNanos(global.appConfig.tonStargiftResaleAmountMin || 0);
|
||||
const tonCommission = selectTonGiftResaleCommission(global);
|
||||
const tonResaleAmountMin = convertTonFromNanos(global.appConfig.tonStargiftResaleAmountMin || 0);
|
||||
const maxTonFromConfig = global.appConfig.tonStargiftResaleAmountMax;
|
||||
const tonStargiftResaleAmountMax = maxTonFromConfig && convertTonFromNanos(maxTonFromConfig);
|
||||
const tonResaleAmountMax = maxTonFromConfig ? convertTonFromNanos(maxTonFromConfig) : undefined;
|
||||
|
||||
const tonUsdRate = global.appConfig.tonUsdRate;
|
||||
|
||||
return {
|
||||
starsStargiftResaleCommissionPermille,
|
||||
starsStargiftResaleAmountMin,
|
||||
starsStargiftResaleAmountMax,
|
||||
starsCommission,
|
||||
starsResaleAmountMin,
|
||||
starsResaleAmountMax,
|
||||
starsUsdWithdrawRate,
|
||||
tonStargiftResaleCommissionPermille,
|
||||
tonStargiftResaleAmountMin,
|
||||
tonStargiftResaleAmountMax,
|
||||
tonCommission,
|
||||
tonResaleAmountMin,
|
||||
tonResaleAmountMax,
|
||||
tonUsdRate,
|
||||
};
|
||||
},
|
||||
|
||||
@ -709,3 +709,29 @@ addActionHandler('openGiftAuctionAcquiredModal', async (global, actions, payload
|
||||
|
||||
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');
|
||||
|
||||
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 => {
|
||||
const { peerId, collectionId, tabId = getCurrentTabId() } = payload;
|
||||
const tabState = selectTabState(global, tabId);
|
||||
|
||||
@ -106,3 +106,12 @@ export function selectActiveGiftsCollectionId<T extends GlobalState>(
|
||||
): ProfileCollectionKey {
|
||||
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;
|
||||
} & WithTabId;
|
||||
closeGiftDescriptionRemoveModal: WithTabId | undefined;
|
||||
openGiftOfferAcceptModal: {
|
||||
peerId: string;
|
||||
messageId: number;
|
||||
gift: ApiStarGiftUnique;
|
||||
price: ApiTypeCurrencyAmount;
|
||||
} & WithTabId;
|
||||
closeGiftOfferAcceptModal: WithTabId | undefined;
|
||||
updateSelectedGiftCollection: {
|
||||
peerId: string;
|
||||
collectionId: number;
|
||||
@ -2805,6 +2812,13 @@ export interface ActionPayloads {
|
||||
hash?: string;
|
||||
} & WithTabId;
|
||||
|
||||
acceptStarGiftOffer: {
|
||||
messageId: number;
|
||||
} & WithTabId;
|
||||
declineStarGiftOffer: {
|
||||
messageId: number;
|
||||
} & WithTabId;
|
||||
|
||||
openStarsGiftModal: ({
|
||||
chatId?: string;
|
||||
forUserId?: string;
|
||||
|
||||
@ -871,6 +871,13 @@ export type TabState = {
|
||||
details: ApiStarGiftAttributeOriginalDetails;
|
||||
};
|
||||
|
||||
giftOfferAcceptModal?: {
|
||||
peerId: string;
|
||||
messageId: number;
|
||||
gift: ApiStarGiftUnique;
|
||||
price: ApiTypeCurrencyAmount;
|
||||
};
|
||||
|
||||
giftUpgradeModal?: {
|
||||
sampleAttributes: ApiStarGiftAttribute[];
|
||||
recipientId?: string;
|
||||
|
||||
@ -1883,6 +1883,7 @@ payments.getUniqueStarGiftValueInfo#4365af6b slug:string = payments.UniqueStarGi
|
||||
payments.checkCanSendGift#c0c4edc9 gift_id:long = payments.CheckCanSendGiftResult;
|
||||
payments.getStarGiftAuctionState#5c9ff4d6 auction:InputStarGiftAuction version:int = payments.StarGiftAuctionState;
|
||||
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.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;
|
||||
|
||||
@ -354,6 +354,7 @@
|
||||
"payments.getStarGiftCollections",
|
||||
"payments.getStarGiftAuctionState",
|
||||
"payments.getStarGiftAuctionAcquiredGifts",
|
||||
"payments.resolveStarGiftOffer",
|
||||
"langpack.getLangPack",
|
||||
"langpack.getStrings",
|
||||
"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;
|
||||
'ActionStarGiftUniqueSymbol': undefined;
|
||||
'ActionStarGiftSelf': undefined;
|
||||
'ActionStarGiftOfferAccepted': undefined;
|
||||
'ActionStarGiftOfferRejected': undefined;
|
||||
'ActionStarGiftOfferHasExpired': undefined;
|
||||
'GiftOfferReject': undefined;
|
||||
'GiftOfferAccept': undefined;
|
||||
'GiftOfferRejectTitle': undefined;
|
||||
'GiftOfferAcceptTitle': undefined;
|
||||
'ActionSuggestedPhotoButton': undefined;
|
||||
'ActionSuggestedVideoTitle': undefined;
|
||||
'ActionSuggestedVideoText': undefined;
|
||||
@ -2271,6 +2278,9 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'GiftInfoPeerDescriptionFreeUpgradeOut': {
|
||||
'peer': V;
|
||||
};
|
||||
'GiftInfoValueAmount': {
|
||||
'amount': V;
|
||||
};
|
||||
'GiftInfoPeerConvertDescription': {
|
||||
'peer': V;
|
||||
'amount': V;
|
||||
@ -2655,6 +2665,16 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'ActionStarGiftTransferredUnknownChannel': {
|
||||
'channel': V;
|
||||
};
|
||||
'ActionStarGiftSoldFromOffer': {
|
||||
'gift': V;
|
||||
'user': V;
|
||||
'cost': V;
|
||||
};
|
||||
'ActionStarGiftBoughtFromOffer': {
|
||||
'user': V;
|
||||
'gift': V;
|
||||
'cost': V;
|
||||
};
|
||||
'ActionStarGiftReceivedAnonymous': {
|
||||
'cost': V;
|
||||
};
|
||||
@ -2719,6 +2739,51 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'ActionStarGiftAuctionBought': {
|
||||
'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': {
|
||||
'user': V;
|
||||
};
|
||||
|
||||
@ -339,6 +339,26 @@ export function formatMediaDuration(duration: number, maxValue?: number) {
|
||||
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) {
|
||||
const parts = [];
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { ApiTypeCurrencyAmount } from '../../api/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 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