Star gift: Support auction (#6541)
This commit is contained in:
parent
ef773026c0
commit
38d7cd0c5a
@ -1,6 +1,7 @@
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type {
|
||||
ApiAuctionBidLevel,
|
||||
ApiDisallowedGiftsSettings,
|
||||
ApiInputSavedStarGift,
|
||||
ApiSavedStarGift,
|
||||
@ -8,10 +9,14 @@ import type {
|
||||
ApiStarGiftAttribute,
|
||||
ApiStarGiftAttributeCounter,
|
||||
ApiStarGiftAttributeId,
|
||||
ApiStarGiftAuctionAcquiredGift,
|
||||
ApiStarGiftAuctionState,
|
||||
ApiStarGiftAuctionUserState,
|
||||
ApiStarGiftCollection,
|
||||
ApiStarGiftUpgradePreview,
|
||||
ApiStarGiftUpgradePrice,
|
||||
ApiTypeResaleStarGifts,
|
||||
ApiTypeStarGiftAuctionState,
|
||||
} from '../../types';
|
||||
|
||||
import { int2hex } from '../../../util/colors';
|
||||
@ -20,6 +25,7 @@ import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import { addDocumentToLocalDb } from '../helpers/localDb';
|
||||
import { buildApiFormattedText } from './common';
|
||||
import { buildApiCurrencyAmount } from './payments';
|
||||
import { buildApiPeerId } from './peers';
|
||||
import { getApiChatIdFromMtpPeer } from './peers';
|
||||
import { buildStickerFromDocument } from './symbols';
|
||||
import { buildApiUser } from './users';
|
||||
@ -57,7 +63,7 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
||||
const {
|
||||
id, limited, stars, availabilityRemains, availabilityTotal, convertStars, firstSaleDate, lastSaleDate, soldOut,
|
||||
birthday, upgradeStars, resellMinStars, title, availabilityResale, releasedBy,
|
||||
requirePremium, limitedPerUser, perUserTotal, perUserRemains, lockedUntilDate,
|
||||
requirePremium, limitedPerUser, perUserTotal, perUserRemains, lockedUntilDate, auction, giftsPerRound, background,
|
||||
} = starGift;
|
||||
|
||||
addDocumentToLocalDb(starGift.sticker);
|
||||
@ -87,6 +93,13 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
||||
perUserTotal,
|
||||
perUserRemains,
|
||||
lockedUntilDate,
|
||||
isAuction: auction,
|
||||
giftsPerRound,
|
||||
background: background ? {
|
||||
centerColor: int2hex(background.centerColor),
|
||||
edgeColor: int2hex(background.edgeColor),
|
||||
textColor: int2hex(background.textColor),
|
||||
} : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@ -159,8 +172,9 @@ export function buildApiStarGiftAttribute(attribute: GramJs.TypeStarGiftAttribut
|
||||
|
||||
export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId: string): ApiSavedStarGift {
|
||||
const {
|
||||
gift, date, convertStars, fromId, message, msgId, nameHidden, unsaved, upgradeStars, transferStars, canUpgrade,
|
||||
savedId, canExportAt, pinnedToTop, canResellAt, canTransferAt, prepaidUpgradeHash, dropOriginalDetailsStars,
|
||||
gift, date, convertStars, fromId, message, msgId, nameHidden, unsaved, refunded, upgradeStars, transferStars,
|
||||
canUpgrade, savedId, canExportAt, pinnedToTop, canResellAt, canTransferAt, prepaidUpgradeHash,
|
||||
dropOriginalDetailsStars,
|
||||
} = userStarGift;
|
||||
|
||||
const inputGift: ApiInputSavedStarGift | undefined = savedId && peerId
|
||||
@ -176,6 +190,7 @@ export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId
|
||||
messageId: msgId,
|
||||
isNameHidden: nameHidden,
|
||||
isUnsaved: unsaved,
|
||||
isRefunded: refunded,
|
||||
canUpgrade,
|
||||
alreadyPaidUpgradeStars: toJSNumber(upgradeStars),
|
||||
transferStars: toJSNumber(transferStars),
|
||||
@ -334,3 +349,104 @@ export function buildApiStarGiftUpgradePreview(
|
||||
nextPrices: result.nextPrices?.map(buildApiStarGiftUpgradePrice) || [],
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiAuctionBidLevel(bidLevel: GramJs.AuctionBidLevel): ApiAuctionBidLevel {
|
||||
return {
|
||||
pos: bidLevel.pos,
|
||||
amount: toJSNumber(bidLevel.amount) ?? 0,
|
||||
date: bidLevel.date,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiTypeStarGiftAuctionState(
|
||||
state: GramJs.TypeStarGiftAuctionState,
|
||||
): ApiTypeStarGiftAuctionState | undefined {
|
||||
if (state instanceof GramJs.StarGiftAuctionStateNotModified) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (state instanceof GramJs.StarGiftAuctionStateFinished) {
|
||||
const {
|
||||
startDate, endDate, averagePrice, listedCount, fragmentListedCount, fragmentListedUrl,
|
||||
} = state;
|
||||
|
||||
return {
|
||||
type: 'finished',
|
||||
startDate,
|
||||
endDate,
|
||||
averagePrice: toJSNumber(averagePrice),
|
||||
listedCount,
|
||||
fragmentListedCount,
|
||||
fragmentListedUrl,
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
version, startDate, endDate, minBidAmount, bidLevels, topBidders,
|
||||
nextRoundAt, lastGiftNum, giftsLeft, currentRound, totalRounds,
|
||||
} = state;
|
||||
|
||||
return {
|
||||
type: 'active',
|
||||
version,
|
||||
startDate,
|
||||
endDate,
|
||||
minBidAmount: toJSNumber(minBidAmount),
|
||||
bidLevels: bidLevels.map(buildApiAuctionBidLevel),
|
||||
topBidders: topBidders.map((id) => buildApiPeerId(id, 'user')),
|
||||
nextRoundAt,
|
||||
lastGiftNum,
|
||||
giftsLeft,
|
||||
currentRound,
|
||||
totalRounds,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiStarGiftAuctionUserState(
|
||||
userState: GramJs.StarGiftAuctionUserState,
|
||||
): ApiStarGiftAuctionUserState {
|
||||
const {
|
||||
returned, bidAmount, bidDate, minBidAmount, bidPeer, acquiredCount,
|
||||
} = userState;
|
||||
|
||||
return {
|
||||
isReturned: returned || undefined,
|
||||
bidAmount: bidAmount !== undefined ? toJSNumber(bidAmount) : undefined,
|
||||
bidDate,
|
||||
minBidAmount: minBidAmount !== undefined ? toJSNumber(minBidAmount) : undefined,
|
||||
bidPeerId: bidPeer && getApiChatIdFromMtpPeer(bidPeer),
|
||||
acquiredCount,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiStarGiftAuctionState(
|
||||
result: GramJs.payments.StarGiftAuctionState,
|
||||
): ApiStarGiftAuctionState | undefined {
|
||||
const gift = buildApiStarGift(result.gift);
|
||||
if (gift.type !== 'starGift') return undefined;
|
||||
|
||||
const state = buildApiTypeStarGiftAuctionState(result.state);
|
||||
if (!state) return undefined;
|
||||
|
||||
return {
|
||||
gift,
|
||||
state,
|
||||
userState: buildApiStarGiftAuctionUserState(result.userState),
|
||||
timeout: result.timeout,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiStarGiftAuctionAcquiredGift(
|
||||
result: GramJs.StarGiftAuctionAcquiredGift,
|
||||
): ApiStarGiftAuctionAcquiredGift {
|
||||
return {
|
||||
peerId: getApiChatIdFromMtpPeer(result.peer),
|
||||
date: result.date,
|
||||
bidAmount: toJSNumber(result.bidAmount),
|
||||
round: result.round,
|
||||
position: result.pos,
|
||||
message: result.message ? buildApiFormattedText(result.message) : undefined,
|
||||
giftNumber: result.giftNum,
|
||||
isNameHidden: result.nameHidden || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@ -390,8 +390,9 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
||||
}
|
||||
if (action instanceof GramJs.MessageActionStarGift) {
|
||||
const {
|
||||
nameHidden, saved, converted, upgraded, refunded, canUpgrade, prepaidUpgrade, gift, message, convertStars,
|
||||
upgradeMsgId, giftMsgId, upgradeStars, fromId, peer, savedId, prepaidUpgradeHash,
|
||||
nameHidden, saved, converted, upgraded, refunded, canUpgrade, prepaidUpgrade, auctionAcquired,
|
||||
gift, message, convertStars, upgradeMsgId, giftMsgId, upgradeStars, fromId, peer, savedId,
|
||||
prepaidUpgradeHash, toId, giftNum,
|
||||
} = action;
|
||||
|
||||
const starGift = buildApiStarGift(gift);
|
||||
@ -407,6 +408,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
||||
isRefunded: refunded,
|
||||
canUpgrade,
|
||||
isPrepaidUpgrade: prepaidUpgrade,
|
||||
isAuctionAcquired: auctionAcquired,
|
||||
gift: starGift,
|
||||
message: message && buildApiFormattedText(message),
|
||||
starsToConvert: toJSNumber(convertStars),
|
||||
@ -417,6 +419,8 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
||||
peerId: peer && getApiChatIdFromMtpPeer(peer),
|
||||
savedId: savedId !== undefined ? buildApiPeerId(savedId, 'user') : undefined,
|
||||
prepaidUpgradeHash,
|
||||
toId: toId && getApiChatIdFromMtpPeer(toId),
|
||||
giftNumber: giftNum,
|
||||
};
|
||||
}
|
||||
if (action instanceof GramJs.MessageActionStarGiftUnique) {
|
||||
|
||||
@ -24,6 +24,7 @@ import type {
|
||||
ApiVoice,
|
||||
ApiWebDocument,
|
||||
ApiWebPage,
|
||||
ApiWebPageAuctionData,
|
||||
ApiWebPageStickerData,
|
||||
ApiWebPageStoryData,
|
||||
BoughtPaidMedia,
|
||||
@ -863,11 +864,16 @@ export function buildWebPage(webPage: GramJs.TypeWebPage): ApiWebPage | undefine
|
||||
}
|
||||
let story: ApiWebPageStoryData | undefined;
|
||||
let gift: ApiStarGiftUnique | undefined;
|
||||
let auction: ApiWebPageAuctionData | undefined;
|
||||
let stickers: ApiWebPageStickerData | undefined;
|
||||
const attributeStory = attributes
|
||||
?.find((a): a is GramJs.WebPageAttributeStory => a instanceof GramJs.WebPageAttributeStory);
|
||||
const attributeGift = attributes
|
||||
?.find((a): a is GramJs.WebPageAttributeUniqueStarGift => a instanceof GramJs.WebPageAttributeUniqueStarGift);
|
||||
const attributeAuction = attributes
|
||||
?.find((a): a is GramJs.WebPageAttributeStarGiftAuction => (
|
||||
a instanceof GramJs.WebPageAttributeStarGiftAuction
|
||||
));
|
||||
if (attributeStory) {
|
||||
const peerId = getApiChatIdFromMtpPeer(attributeStory.peer);
|
||||
story = {
|
||||
@ -883,6 +889,15 @@ export function buildWebPage(webPage: GramJs.TypeWebPage): ApiWebPage | undefine
|
||||
const starGift = buildApiStarGift(attributeGift.gift);
|
||||
gift = starGift.type === 'starGiftUnique' ? starGift : undefined;
|
||||
}
|
||||
if (attributeAuction) {
|
||||
const starGift = buildApiStarGift(attributeAuction.gift);
|
||||
if (starGift.type === 'starGift') {
|
||||
auction = {
|
||||
gift: starGift,
|
||||
endDate: attributeAuction.endDate,
|
||||
};
|
||||
}
|
||||
}
|
||||
const attributeStickers = attributes?.find((a): a is GramJs.WebPageAttributeStickerSet => (
|
||||
a instanceof GramJs.WebPageAttributeStickerSet
|
||||
));
|
||||
@ -914,6 +929,7 @@ export function buildWebPage(webPage: GramJs.TypeWebPage): ApiWebPage | undefine
|
||||
audio,
|
||||
story,
|
||||
gift,
|
||||
auction,
|
||||
stickers,
|
||||
};
|
||||
}
|
||||
|
||||
@ -553,7 +553,7 @@ export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction):
|
||||
const {
|
||||
date, id, peer, amount, description, photo, title, refund, extendedMedia, failed, msgId, pending, gift, reaction,
|
||||
subscriptionPeriod, stargift, giveawayPostId, starrefCommissionPermille, stargiftUpgrade, paidMessages,
|
||||
stargiftResale, postsSearch, stargiftPrepaidUpgrade, stargiftDropOriginalDetails,
|
||||
stargiftResale, postsSearch, stargiftPrepaidUpgrade, stargiftDropOriginalDetails, stargiftAuctionBid,
|
||||
} = transaction;
|
||||
|
||||
if (photo) {
|
||||
@ -595,6 +595,7 @@ export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction):
|
||||
isPostsSearch: postsSearch,
|
||||
isDropOriginalDetails: stargiftDropOriginalDetails,
|
||||
isPrepaidUpgrade: stargiftPrepaidUpgrade,
|
||||
isStarGiftAuctionBid: stargiftAuctionBid,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -798,6 +798,20 @@ export function buildInputInvoice(invoice: ApiRequestInputInvoice) {
|
||||
});
|
||||
}
|
||||
|
||||
case 'stargiftAuctionBid': {
|
||||
const {
|
||||
giftId, bidAmount, peer, message, shouldHideName, isUpdateBid,
|
||||
} = invoice;
|
||||
return new GramJs.InputInvoiceStarGiftAuctionBid({
|
||||
giftId: BigInt(giftId),
|
||||
bidAmount: BigInt(bidAmount),
|
||||
peer: peer && buildInputPeer(peer.id, peer.accessHash || ''),
|
||||
message: message && buildInputTextWithEntities(message),
|
||||
hideName: shouldHideName || undefined,
|
||||
updateBid: isUpdateBid || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
case 'giveaway':
|
||||
default: {
|
||||
const purpose = buildInputStorePaymentPurpose(invoice.purpose);
|
||||
|
||||
@ -18,6 +18,8 @@ import {
|
||||
buildApiResaleGifts,
|
||||
buildApiSavedStarGift,
|
||||
buildApiStarGift,
|
||||
buildApiStarGiftAuctionAcquiredGift,
|
||||
buildApiStarGiftAuctionState,
|
||||
buildApiStarGiftCollection,
|
||||
buildApiStarGiftUpgradePreview,
|
||||
buildInputResaleGiftsAttributes,
|
||||
@ -412,6 +414,51 @@ export async function fetchStarGiftUpgradePreview({
|
||||
return buildApiStarGiftUpgradePreview(result);
|
||||
}
|
||||
|
||||
export async function fetchStarGiftAuctionState({
|
||||
giftId,
|
||||
slug,
|
||||
version = 0,
|
||||
}: {
|
||||
giftId?: string;
|
||||
slug?: string;
|
||||
version?: number;
|
||||
}) {
|
||||
if (!giftId && !slug) return undefined;
|
||||
|
||||
const auction = slug
|
||||
? new GramJs.InputStarGiftAuctionSlug({ slug })
|
||||
: new GramJs.InputStarGiftAuction({ giftId: BigInt(giftId!) });
|
||||
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarGiftAuctionState({
|
||||
auction,
|
||||
version,
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return buildApiStarGiftAuctionState(result);
|
||||
}
|
||||
|
||||
export async function fetchStarGiftAuctionAcquiredGifts({
|
||||
giftId,
|
||||
}: {
|
||||
giftId: string;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarGiftAuctionAcquiredGifts({
|
||||
giftId: BigInt(giftId),
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
gifts: result.gifts.map(buildApiStarGiftAuctionAcquiredGift),
|
||||
};
|
||||
}
|
||||
|
||||
export function upgradeStarGift({
|
||||
inputSavedGift,
|
||||
shouldKeepOriginalDetails,
|
||||
|
||||
@ -31,6 +31,7 @@ import {
|
||||
buildApiFormattedText,
|
||||
buildApiPhoto, buildApiUsernames, buildPrivacyRules,
|
||||
} from '../apiBuilders/common';
|
||||
import { buildApiStarGiftAuctionUserState, buildApiTypeStarGiftAuctionState } from '../apiBuilders/gifts';
|
||||
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
||||
import {
|
||||
buildApiMessageExtendedMediaPreview,
|
||||
@ -1095,6 +1096,22 @@ export function updater(update: Update) {
|
||||
'@type': 'updateStarsBalance',
|
||||
balance,
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdateStarGiftAuctionState) {
|
||||
const state = buildApiTypeStarGiftAuctionState(update.state);
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
sendApiUpdate({
|
||||
'@type': 'updateStarGiftAuctionState',
|
||||
giftId: update.giftId.toString(),
|
||||
state,
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdateStarGiftAuctionUserState) {
|
||||
sendApiUpdate({
|
||||
'@type': 'updateStarGiftAuctionUserState',
|
||||
giftId: update.giftId.toString(),
|
||||
userState: buildApiStarGiftAuctionUserState(update.userState),
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdatePaidReactionPrivacy) {
|
||||
sendApiUpdate({
|
||||
'@type': 'updatePaidReactionPrivacy',
|
||||
|
||||
@ -243,6 +243,7 @@ export interface ApiMessageActionStarGift extends ActionMediaType {
|
||||
isRefunded?: true;
|
||||
canUpgrade?: true;
|
||||
isPrepaidUpgrade?: true;
|
||||
isAuctionAcquired?: true;
|
||||
gift: ApiStarGiftRegular;
|
||||
message?: ApiFormattedText;
|
||||
starsToConvert?: number;
|
||||
@ -253,6 +254,8 @@ export interface ApiMessageActionStarGift extends ActionMediaType {
|
||||
peerId?: string;
|
||||
savedId?: string;
|
||||
prepaidUpgradeHash?: string;
|
||||
toId?: string;
|
||||
giftNumber?: number;
|
||||
}
|
||||
|
||||
export interface ApiMessageActionStarGiftUnique extends ActionMediaType {
|
||||
|
||||
@ -10,7 +10,7 @@ import type {
|
||||
ApiLabeledPrice,
|
||||
} from './payments';
|
||||
import type { ApiTypePeerColor } from './peers';
|
||||
import type { ApiStarGiftUnique, ApiTypeCurrencyAmount } from './stars';
|
||||
import type { ApiStarGiftRegular, ApiStarGiftUnique, ApiTypeCurrencyAmount } from './stars';
|
||||
import type {
|
||||
ApiMessageStoryData, ApiStory, ApiWebPageStickerData, ApiWebPageStoryData,
|
||||
} from './stories';
|
||||
@ -394,10 +394,16 @@ export interface ApiWebPageFull {
|
||||
video?: ApiVideo;
|
||||
story?: ApiWebPageStoryData;
|
||||
gift?: ApiStarGiftUnique;
|
||||
auction?: ApiWebPageAuctionData;
|
||||
stickers?: ApiWebPageStickerData;
|
||||
hasLargeMedia?: boolean;
|
||||
}
|
||||
|
||||
export type ApiWebPageAuctionData = {
|
||||
gift: ApiStarGiftRegular;
|
||||
endDate: number;
|
||||
};
|
||||
|
||||
export type ApiWebPage = ApiWebPagePending | ApiWebPageEmpty | ApiWebPageFull;
|
||||
|
||||
/**
|
||||
|
||||
@ -412,11 +412,22 @@ export type ApiInputInvoiceStarGiftPrepaidUpgrade = {
|
||||
hash: string;
|
||||
};
|
||||
|
||||
export type ApiInputInvoiceStarGiftAuctionBid = {
|
||||
type: 'stargiftAuctionBid';
|
||||
giftId: string;
|
||||
bidAmount: number;
|
||||
peerId?: string;
|
||||
message?: ApiFormattedText;
|
||||
shouldHideName?: boolean;
|
||||
isUpdateBid?: boolean;
|
||||
};
|
||||
|
||||
export type ApiInputInvoice = ApiInputInvoiceMessage | ApiInputInvoiceSlug | ApiInputInvoiceGiveaway
|
||||
| ApiInputInvoiceGiftCode | ApiInputInvoicePremiumGiftStars | ApiInputInvoiceStars | ApiInputInvoiceStarsGift
|
||||
| ApiInputInvoiceStarsGiveaway | ApiInputInvoiceStarGift | ApiInputInvoiceChatInviteSubscription
|
||||
| ApiInputInvoiceStarGiftUpgrade | ApiInputInvoiceStarGiftTransfer | ApiInputInvoiceStarGiftResale
|
||||
| ApiInputInvoiceStarGiftDropOriginalDetails | ApiInputInvoiceStarGiftPrepaidUpgrade;
|
||||
| ApiInputInvoiceStarGiftDropOriginalDetails | ApiInputInvoiceStarGiftPrepaidUpgrade
|
||||
| ApiInputInvoiceStarGiftAuctionBid;
|
||||
|
||||
/* Used for Invoice request */
|
||||
export type ApiRequestInputInvoiceMessage = {
|
||||
@ -497,12 +508,23 @@ export type ApiRequestInputInvoiceStarGiftPrepaidUpgrade = {
|
||||
hash: string;
|
||||
};
|
||||
|
||||
export type ApiRequestInputInvoiceStarGiftAuctionBid = {
|
||||
type: 'stargiftAuctionBid';
|
||||
giftId: string;
|
||||
bidAmount: number;
|
||||
peer?: ApiPeer;
|
||||
message?: ApiFormattedText;
|
||||
shouldHideName?: boolean;
|
||||
isUpdateBid?: boolean;
|
||||
};
|
||||
|
||||
export type ApiRequestInputInvoice = ApiRequestInputInvoiceMessage | ApiRequestInputInvoiceSlug
|
||||
| ApiRequestInputInvoiceGiveaway | ApiRequestInputInvoiceStars | ApiRequestInputInvoiceStarsGiveaway
|
||||
| ApiRequestInputInvoiceChatInviteSubscription | ApiRequestInputInvoiceStarGift
|
||||
| ApiRequestInputInvoiceStarGiftUpgrade | ApiRequestInputInvoiceStarGiftTransfer
|
||||
| ApiRequestInputInvoicePremiumGiftStars | ApiRequestInputInvoiceStarGiftResale
|
||||
| ApiRequestInputInvoiceStarGiftDropOriginalDetails | ApiRequestInputInvoiceStarGiftPrepaidUpgrade;
|
||||
| ApiRequestInputInvoiceStarGiftDropOriginalDetails | ApiRequestInputInvoiceStarGiftPrepaidUpgrade
|
||||
| ApiRequestInputInvoiceStarGiftAuctionBid;
|
||||
|
||||
export interface ApiUniqueStarGiftValueInfo {
|
||||
isLastSaleOnFragment?: true;
|
||||
|
||||
@ -27,6 +27,15 @@ export interface ApiStarGiftRegular {
|
||||
perUserTotal?: number;
|
||||
perUserRemains?: number;
|
||||
lockedUntilDate?: number;
|
||||
isAuction?: true;
|
||||
giftsPerRound?: number;
|
||||
background?: ApiStarGiftBackground;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftBackground {
|
||||
centerColor: string;
|
||||
edgeColor: string;
|
||||
textColor: string;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftUnique {
|
||||
@ -103,6 +112,7 @@ export interface ApiStarGiftUpgradePreview {
|
||||
export interface ApiSavedStarGift {
|
||||
isNameHidden?: boolean;
|
||||
isUnsaved?: boolean;
|
||||
isRefunded?: boolean;
|
||||
fromId?: string;
|
||||
date: number;
|
||||
gift: ApiStarGift;
|
||||
@ -259,6 +269,7 @@ export interface ApiStarsTransaction {
|
||||
isPostsSearch?: true;
|
||||
isDropOriginalDetails?: true;
|
||||
isPrepaidUpgrade?: true;
|
||||
isStarGiftAuctionBid?: true;
|
||||
}
|
||||
|
||||
export interface ApiStarsSubscription {
|
||||
@ -315,3 +326,63 @@ export interface ApiStarsRating {
|
||||
stars: number;
|
||||
nextLevelStars?: number;
|
||||
}
|
||||
|
||||
export interface ApiAuctionBidLevel {
|
||||
pos: number;
|
||||
amount: number;
|
||||
date: number;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftAuctionStateActive {
|
||||
type: 'active';
|
||||
version: number;
|
||||
startDate: number;
|
||||
endDate: number;
|
||||
minBidAmount: number;
|
||||
bidLevels: ApiAuctionBidLevel[];
|
||||
topBidders: string[];
|
||||
nextRoundAt: number;
|
||||
lastGiftNum: number;
|
||||
giftsLeft: number;
|
||||
currentRound: number;
|
||||
totalRounds: number;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftAuctionStateFinished {
|
||||
type: 'finished';
|
||||
startDate: number;
|
||||
endDate: number;
|
||||
averagePrice: number;
|
||||
listedCount?: number;
|
||||
fragmentListedCount?: number;
|
||||
fragmentListedUrl?: string;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftAuctionUserState {
|
||||
isReturned?: true;
|
||||
bidAmount?: number;
|
||||
bidDate?: number;
|
||||
minBidAmount?: number;
|
||||
bidPeerId?: string;
|
||||
acquiredCount: number;
|
||||
}
|
||||
|
||||
export type ApiTypeStarGiftAuctionState = ApiStarGiftAuctionStateActive | ApiStarGiftAuctionStateFinished;
|
||||
|
||||
export interface ApiStarGiftAuctionState {
|
||||
gift: ApiStarGiftRegular;
|
||||
state: ApiTypeStarGiftAuctionState;
|
||||
userState: ApiStarGiftAuctionUserState;
|
||||
timeout: number;
|
||||
}
|
||||
|
||||
export interface ApiStarGiftAuctionAcquiredGift {
|
||||
peerId: string;
|
||||
date: number;
|
||||
bidAmount: number;
|
||||
round: number;
|
||||
position: number;
|
||||
message?: ApiFormattedText;
|
||||
giftNumber?: number;
|
||||
isNameHidden?: true;
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ import type {
|
||||
} from './misc';
|
||||
import type { ApiEmojiStatusType, ApiPeerSettings } from './peers';
|
||||
import type { ApiPrivacyKey, LangPackStringValue, PrivacyVisibility } from './settings';
|
||||
import type { ApiTypeCurrencyAmount } from './stars';
|
||||
import type { ApiStarGiftAuctionUserState, ApiTypeCurrencyAmount, ApiTypeStarGiftAuctionState } from './stars';
|
||||
import type { ApiStealthMode, ApiStory, ApiStorySkipped } from './stories';
|
||||
import type {
|
||||
ApiUser, ApiUserFullInfo, ApiUserStatus,
|
||||
@ -834,6 +834,18 @@ export type ApiUpdateStarsBalance = {
|
||||
balance: ApiTypeCurrencyAmount;
|
||||
};
|
||||
|
||||
export type ApiUpdateStarGiftAuctionState = {
|
||||
'@type': 'updateStarGiftAuctionState';
|
||||
giftId: string;
|
||||
state: ApiTypeStarGiftAuctionState;
|
||||
};
|
||||
|
||||
export type ApiUpdateStarGiftAuctionUserState = {
|
||||
'@type': 'updateStarGiftAuctionUserState';
|
||||
giftId: string;
|
||||
userState: ApiStarGiftAuctionUserState;
|
||||
};
|
||||
|
||||
export type ApiUpdateDeleteProfilePhoto = {
|
||||
'@type': 'updateDeleteProfilePhoto';
|
||||
peerId: string;
|
||||
@ -915,10 +927,11 @@ export type ApiUpdate = (
|
||||
ApiRequestReconnectApi | ApiRequestSync | ApiUpdateFetchingDifference | ApiUpdateChannelMessages |
|
||||
ApiUpdateStealthMode | ApiUpdateAttachMenuBots | ApiUpdateNewAuthorization | ApiUpdateGroupInvitePrivacyForbidden |
|
||||
ApiUpdateViewForumAsMessages | ApiUpdateSavedDialogPinned | ApiUpdatePinnedSavedDialogIds | ApiUpdateChatLastMessage |
|
||||
ApiUpdateDeleteSavedHistory | ApiUpdatePremiumFloodWait | ApiUpdateStarsBalance | ApiUpdateBotCommands |
|
||||
ApiUpdateQuickReplyMessage | ApiUpdateQuickReplies | ApiDeleteQuickReply | ApiUpdateDeleteQuickReplyMessages |
|
||||
ApiUpdateDeleteProfilePhoto | ApiUpdateNewProfilePhoto | ApiUpdateEntities | ApiUpdatePaidReactionPrivacy |
|
||||
ApiUpdateLangPackTooLong | ApiUpdateLangPack | ApiUpdateNotSupportedInFrozenAccountError
|
||||
ApiUpdateDeleteSavedHistory | ApiUpdatePremiumFloodWait | ApiUpdateStarsBalance | ApiUpdateStarGiftAuctionState
|
||||
| ApiUpdateStarGiftAuctionUserState | ApiUpdateBotCommands | ApiUpdateQuickReplyMessage | ApiUpdateQuickReplies
|
||||
| ApiDeleteQuickReply | ApiUpdateDeleteQuickReplyMessages | ApiUpdateDeleteProfilePhoto | ApiUpdateNewProfilePhoto
|
||||
| ApiUpdateEntities | ApiUpdatePaidReactionPrivacy | ApiUpdateLangPackTooLong | ApiUpdateLangPack
|
||||
| ApiUpdateNotSupportedInFrozenAccountError
|
||||
);
|
||||
|
||||
export type OnApiUpdate = (update: ApiUpdate) => void;
|
||||
|
||||
1
src/assets/font-icons/auction-drop.svg
Normal file
1
src/assets/font-icons/auction-drop.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M29.556 13.557C29.556 6.082 23.474 0 16 0S2.444 6.082 2.444 13.557v.543l9.13 6.917a3.67 3.67 0 0 0-1.453 2.911v4.387A3.69 3.69 0 0 0 13.806 32h4.387a3.69 3.69 0 0 0 3.685-3.685v-4.387a3.67 3.67 0 0 0-1.453-2.91l9.131-6.918zm-18.558.848 1.859 4.842-7.25-5.493c.06-.035.127-.071.183-.105.492-.3.818-.498 1.591-.498s1.099.198 1.59.498c.486.294 1.086.634 2.027.756m4.583 5.839-2.314-6.03a5.7 5.7 0 0 0 1.154-.567c.492-.298.816-.496 1.587-.496s1.095.198 1.587.496a5.7 5.7 0 0 0 1.157.57l-2.313 6.027zm5.44-5.84c.938-.122 1.536-.462 2.018-.757.492-.298.816-.496 1.587-.496s1.094.198 1.585.496c.056.035.123.071.182.106l-7.221 5.472zM16 2.187c5.637 0 10.32 4.126 11.21 9.514-.574-.347-1.299-.735-2.583-.735-1.384 0-2.127.453-2.724.816-.49.299-.814.496-1.585.496s-1.094-.197-1.585-.496c-.597-.363-1.34-.816-2.724-.816s-2.126.453-2.723.816c-.492.299-.816.496-1.587.496-.773 0-1.098-.197-1.59-.497-.598-.363-1.341-.815-2.727-.815-1.288 0-2.014.39-2.591.737.89-5.39 5.573-9.516 11.21-9.516m3.693 26.13a1.5 1.5 0 0 1-1.5 1.498h-4.386a1.5 1.5 0 0 1-1.499-1.499v-4.387a1.5 1.5 0 0 1 1.499-1.498h4.387a1.5 1.5 0 0 1 1.499 1.498z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
1
src/assets/font-icons/auction-filled.svg
Normal file
1
src/assets/font-icons/auction-filled.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 32 32"><path d="m23.632 12.635-6.73 6.73a.697.697 0 0 1-.986 0l-6.149-6.15a.697.697 0 0 1 0-.986l6.73-6.73a.697.697 0 0 1 .986 0l6.149 6.15a.697.697 0 0 1 0 .986M28.447 11.533a2.34 2.34 0 0 1-3.307 0L17.6 3.992A2.338 2.338 0 1 1 20.906.685l7.54 7.541a2.34 2.34 0 0 1 0 3.307M31.159 26.892a2.87 2.87 0 0 1-4.06 0l-7.528-7.527a.697.697 0 0 1 0-.987l3.075-3.074a.697.697 0 0 1 .986 0l7.527 7.527a2.87 2.87 0 0 1 0 4.06M15.771 24.209a2.34 2.34 0 0 1-3.306 0l-7.542-7.542A2.338 2.338 0 1 1 8.23 13.36l7.541 7.542a2.34 2.34 0 0 1 0 3.307M18.896 29.805v1.498A.697.697 0 0 1 18.2 32H.697A.697.697 0 0 1 0 31.303v-1.498c0-1.37 1.11-2.481 2.481-2.481h13.934c1.37 0 2.481 1.11 2.481 2.48"/></svg>
|
||||
|
After Width: | Height: | Size: 750 B |
1
src/assets/font-icons/auction-next-round.svg
Normal file
1
src/assets/font-icons/auction-next-round.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 32 32"><path d="M20.989 22.53q-.163 0-.322-.02a2.53 2.53 0 0 1-1.641-.914l-.954-1.171-1.468.258a2.52 2.52 0 0 1-2.919-1.952 2.5 2.5 0 0 1 .4-1.963l.73-1.033.028-.04a4 4 0 0 1-.194-.291l-.526-.885a2.52 2.52 0 0 1-.264-1.946 2.47 2.47 0 0 1 1.217-1.529l.182-.087a2.56 2.56 0 0 1 1.727-.115l1.43.413.972-1.013c.933-.975 2.511-1.029 3.52-.117a2.52 2.52 0 0 1 .829 1.735l.072 1.403 1.366.629c.61.28 1.09.803 1.322 1.433a2.47 2.47 0 0 1-.085 1.909 2.5 2.5 0 0 1-1.457 1.304l-1.32.45-.145 1.323a2.47 2.47 0 0 1-.934 1.681 2.55 2.55 0 0 1-1.566.539m-2.083-4.347 1.72 2.11a.48.48 0 0 0 .306.172c.13.012.252-.02.35-.097a.4.4 0 0 0 .156-.285l.291-2.625 2.56-.872a.44.44 0 0 0 .253-.225.4.4 0 0 0 .017-.327.48.48 0 0 0-.247-.269l-2.5-1.15-.137-2.649a.45.45 0 0 0-.148-.31c-.168-.15-.46-.187-.649.012l-1.836 1.916-2.631-.76a.47.47 0 0 0-.311.015l-.089.044c-.088.049-.142.159-.164.238a.44.44 0 0 0 .048.343l.526.882c.12.204.284.38.482.526l1.175.862-1.203.822q-.221.151-.375.37l-.73 1.032a.43.43 0 0 0-.068.337c.058.268.327.39.544.357z" class="st3"/><path d="M20.139 27.861c-6.54 0-11.862-5.321-11.862-11.861S13.6 4.139 20.14 4.139 32 9.46 32 16s-5.321 11.861-11.861 11.861m0-21.66c-5.403 0-9.799 4.396-9.799 9.799s4.396 9.798 9.799 9.798 9.798-4.395 9.798-9.798-4.395-9.798-9.798-9.798M8.169 9.296H2.797a.604.604 0 0 1-.604-.605v-.854c0-.333.27-.604.604-.604H8.17c.333 0 .604.27.604.604v.854c0 .334-.27.605-.604.605M8.169 24.767H2.797a.604.604 0 0 1-.604-.604v-.854c0-.334.27-.605.604-.605H8.17c.333 0 .604.27.604.605v.854c0 .333-.27.604-.604.604M5.975 17.031H.605A.604.604 0 0 1 0 16.427v-.854c0-.334.27-.604.604-.604h5.37c.335 0 .605.27.605.604v.854c0 .334-.27.604-.604.604"/></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" x="0" y="0" version="1.1" viewBox="0 0 32 32"><style>.st3{fill:#4e8ee5}</style><path d="M11.1 18c-7.4 0-10 4.3-10.9 6.8-.4 1-.2 2.1.4 2.9.7 1 1.9 1.6 3.1 1.6h14.8c1.3 0 2.4-.6 3.1-1.6.6-.9.7-1.9.4-2.9-.9-2.5-3.5-6.8-10.9-6.8m8.6 8.4c-.2.3-.7.5-1.2.5H3.7c-.5 0-1-.2-1.2-.5-.2-.2-.2-.5-.1-.8.9-2.4 3.1-5.2 8.7-5.2s7.8 2.8 8.7 5.2c.1.3.1.5-.1.8M31.8 23.9c-.8-2.3-3-6.1-9.6-6.1h-.8c-.7 0-1.2.6-1.1 1.2 0 .7.6 1.2 1.2 1.1h.7c4.8 0 6.6 2.4 7.3 4.5.1.2 0 .3-.1.4-.1.2-.4.3-.8.3h-4.3c-.7 0-1.2.5-1.2 1.2s.5 1.2 1.2 1.2h4.3c1.1 0 2.1-.5 2.7-1.3.7-.7.8-1.6.5-2.5M4.6 11.2l.9.7c.1.1.2.2.3.2v.1l-.4 1.4c-.2.8-.1 1.6.4 2.3.8 1.3 2.6 1.7 3.9.9l1.5-.9 1.5.9c.5.3 1 .4 1.5.4.2 0 .4 0 .7-.1.8-.2 1.4-.7 1.8-1.3.4-.7.5-1.4.3-2.2l-.4-1.5.8-.6c.2.4.4.8.8 1l.4.3-.1.5c-.2.7-.1 1.5.3 2.1.8 1.2 2.4 1.6 3.7.9l.6-.3.6.4c.4.2.9.4 1.4.4.2 0 .4 0 .6-.1.7-.2 1.3-.6 1.7-1.2s.5-1.4.3-2.1l-.1-.5.4-.4c.6-.5.9-1.1 1-1.9.1-.7-.2-1.4-.6-2-.5-.6-1.2-.9-1.9-1l-.7-.1-.4-.8c-.3-.5-.8-1-1.4-1.2-1.3-.5-2.7 0-3.4 1.2l-.4.8h-.7c-.3 0-.6.1-.9.2-.1-.3-.3-.6-.5-.9-.5-.6-1.2-1-2-1l-1.6.2-.6-1.5c-.3-.7-.9-1.3-1.6-1.5-1.5-.7-3.2 0-3.8 1.5L7.9 6l-1.7.1c-.7 0-1.3.3-1.8.8l-.2.2c-.5.6-.8 1.3-.7 2.1s.5 1.5 1.1 2m14.9-.9c.1-.1.2-.2.3-.2l2.1-.2.8-1.9c.1-.2.3-.2.4-.1.2.1.2.1.3.1l.8 1.9 2.1.2c.1 0 .2.1.3.1.1.1.1.1.1.2s-.1.2-.1.2L25 11.9l.5 2v.2l-.2.2H25l-1.8-1.1-1.8 1c-.1.1-.4.1-.5-.1 0-.1-.1-.2 0-.3l.3-1.3.1-.1 2.1-1.2c.1-.1 0-.2-.1-.2l-2.5.1h-.1c-.1 0-.2-.1-.2-.2l-.6-.5c-.1-.1-.1-.2-.1-.2-.3.3-.3.2-.3.1M5.9 8.7c.2-.2.3-.2.4-.2l3.1-.2 1.2-2.8c.1-.3.5-.4.7-.3l.3.3 1.2 2.8 3.2.2c.1 0 .3.1.4.2s.1.2.1.4c0 .1-.1.3-.2.3l-2.4 2 .7 2.9c0 .1 0 .3-.1.4s-.2.2-.3.2-.3 0-.4-.1l-2.7-1.6-2.7 1.6c-.2.1-.6.1-.7-.2-.1-.1-.1-.3-.1-.4l.4-1.4c.1-.2.1-.4.3-.6l3.9-2c.1-.1.1-.3-.1-.2l-4.5.4c-.3-.1-.5-.2-.7-.4L6 9.3c-.1-.1-.1-.2-.1-.3z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" x="0" y="0" version="1.1" viewBox="0 0 32 32"><path d="M11.1 18c-7.4 0-10 4.3-10.9 6.8-.4 1-.2 2.1.4 2.9.7 1 1.9 1.6 3.1 1.6h14.8c1.3 0 2.4-.6 3.1-1.6.6-.9.7-1.9.4-2.9-.9-2.5-3.5-6.8-10.9-6.8m8.6 8.4c-.2.3-.7.5-1.2.5H3.7c-.5 0-1-.2-1.2-.5-.2-.2-.2-.5-.1-.8.9-2.4 3.1-5.2 8.7-5.2s7.8 2.8 8.7 5.2c.1.3.1.5-.1.8M31.8 23.9c-.8-2.3-3-6.1-9.6-6.1h-.8c-.7 0-1.2.6-1.1 1.2 0 .7.6 1.2 1.2 1.1h.7c4.8 0 6.6 2.4 7.3 4.5.1.2 0 .3-.1.4-.1.2-.4.3-.8.3h-4.3c-.7 0-1.2.5-1.2 1.2s.5 1.2 1.2 1.2h4.3c1.1 0 2.1-.5 2.7-1.3.7-.7.8-1.6.5-2.5M4.6 11.2l.9.7c.1.1.2.2.3.2v.1l-.4 1.4c-.2.8-.1 1.6.4 2.3.8 1.3 2.6 1.7 3.9.9l1.5-.9 1.5.9c.5.3 1 .4 1.5.4.2 0 .4 0 .7-.1.8-.2 1.4-.7 1.8-1.3.4-.7.5-1.4.3-2.2l-.4-1.5.8-.6c.2.4.4.8.8 1l.4.3-.1.5c-.2.7-.1 1.5.3 2.1.8 1.2 2.4 1.6 3.7.9l.6-.3.6.4c.4.2.9.4 1.4.4.2 0 .4 0 .6-.1.7-.2 1.3-.6 1.7-1.2s.5-1.4.3-2.1l-.1-.5.4-.4c.6-.5.9-1.1 1-1.9.1-.7-.2-1.4-.6-2-.5-.6-1.2-.9-1.9-1l-.7-.1-.4-.8c-.3-.5-.8-1-1.4-1.2-1.3-.5-2.7 0-3.4 1.2l-.4.8h-.7c-.3 0-.6.1-.9.2-.1-.3-.3-.6-.5-.9-.5-.6-1.2-1-2-1l-1.6.2-.6-1.5c-.3-.7-.9-1.3-1.6-1.5-1.5-.7-3.2 0-3.8 1.5L7.9 6l-1.7.1c-.7 0-1.3.3-1.8.8l-.2.2c-.5.6-.8 1.3-.7 2.1s.5 1.5 1.1 2m14.9-.9c.1-.1.2-.2.3-.2l2.1-.2.8-1.9c.1-.2.3-.2.4-.1.2.1.2.1.3.1l.8 1.9 2.1.2c.1 0 .2.1.3.1.1.1.1.1.1.2s-.1.2-.1.2L25 11.9l.5 2v.2l-.2.2H25l-1.8-1.1-1.8 1c-.1.1-.4.1-.5-.1 0-.1-.1-.2 0-.3l.3-1.3.1-.1 2.1-1.2c.1-.1 0-.2-.1-.2l-2.5.1h-.1c-.1 0-.2-.1-.2-.2l-.6-.5c-.1-.1-.1-.2-.1-.2-.3.3-.3.2-.3.1M5.9 8.7c.2-.2.3-.2.4-.2l3.1-.2 1.2-2.8c.1-.3.5-.4.7-.3l.3.3 1.2 2.8 3.2.2c.1 0 .3.1.4.2s.1.2.1.4c0 .1-.1.3-.2.3l-2.4 2 .7 2.9c0 .1 0 .3-.1.4s-.2.2-.3.2-.3 0-.4-.1l-2.7-1.6-2.7 1.6c-.2.1-.6.1-.7-.2-.1-.1-.1-.3-.1-.4l.4-1.4c.1-.2.1-.4.3-.6l3.9-2c.1-.1.1-.3-.1-.2l-4.5.4c-.3-.1-.5-.2-.7-.4L6 9.3c-.1-.1-.1-.2-.1-.3z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@ -1513,6 +1513,7 @@
|
||||
"GiftInfoDescriptionUpgrade2" = "Upgrade this gift to turn it to a unique collectible.";
|
||||
"GiftInfoPeerDescriptionFreeUpgradeOut" = "{peer} can turn this gift into a unique collectible";
|
||||
"GiftInfoDescriptionUpgraded" = "This gift was turned into a unique collectible.";
|
||||
"GiftInfoDescriptionRefunded" = "This gift was downgraded because a request to refund the payment related to this gift was made, and the money was returned.";
|
||||
"GiftInfoFrom" = "From";
|
||||
"GiftInfoDate" = "Date";
|
||||
"GiftInfoValue" = "Value";
|
||||
@ -1929,6 +1930,9 @@
|
||||
"ActionStarGiftUniqueBackdrop" = "Backdrop";
|
||||
"ActionStarGiftUniqueSymbol" = "Symbol";
|
||||
"ActionStarGiftSelf" = "Saved Gift";
|
||||
"ActionStarGiftAuctionWon" = "You won the auction with a bid of {cost}";
|
||||
"ActionStarGiftAuctionFor" = "Gift for {peer}";
|
||||
"ActionStarGiftAuctionBought" = "You bought this gift at auction for {cost}.";
|
||||
"ActionSuggestedPhotoYou" = "You suggested this photo for {user}'s Telegram profile.";
|
||||
"ActionSuggestedPhoto" = "{user} suggests this photo for your Telegram profile.";
|
||||
"ActionSuggestedPhotoButton" = "View Photo";
|
||||
@ -2343,6 +2347,8 @@
|
||||
"StarGiftReasonDropOriginalDetails" = "Removed Description";
|
||||
"GiftAnUpgradeButton" = "Gift an Upgrade";
|
||||
"GiftPrepaidUpgradeTransactionTitle" = "Gift Upgrade";
|
||||
"StarGiftAuctionBidTransaction" = "Auction Bid";
|
||||
"StarGiftAuctionBidRefundedTransaction" = "Refunded Auction Bid";
|
||||
"ActionStarGiftPrepaidUpgraded" = "{user} turned the gift into a unique collectible";
|
||||
"ActionStarGiftPrepaidUpgradedYou" = "You turned the gift into a unique collectible";
|
||||
"UserNoteTitle" = "Notes";
|
||||
@ -2405,6 +2411,66 @@
|
||||
"StarGiftUpgradeCostModalTitle" = "Upgrade Cost";
|
||||
"StarGiftUpgradeCostHint" = "Users who upgrade their gifts first get collectibles with lower numbers.";
|
||||
"StarGiftPriceDecreaseTimer" = "Price decreases in {timer}";
|
||||
"GiftRibbonAuction" = "auction";
|
||||
"GiftAuctionJoin" = "Join";
|
||||
"GiftAuctionLearnMore" = "Learn More >";
|
||||
"GiftAuctionTopBidders_one" = "Top **{count}** bidder will get **{gift}** gift this round. {link}";
|
||||
"GiftAuctionTopBidders_other" = "Top **{count}** bidders will get **{gift}** gifts this round. {link}";
|
||||
"GiftAuctionStarted" = "Started";
|
||||
"GiftAuctionEnds" = "Ends";
|
||||
"GiftAuctionCurrentRound" = "Current Round";
|
||||
"GiftAuctionRoundValue" = "{current} of {total}";
|
||||
"GiftAuctionDescription_one" = "{count} gift is dropped at varying intervals to the top {count} bidder by bid amount. {link}";
|
||||
"GiftAuctionDescription_other" = "{count} gifts are dropped at varying intervals to the top {count} bidders by bid amount. {link}";
|
||||
"GiftAuctionPlaceBid" = "Place a Bid";
|
||||
"GiftAuctionPlaceBidButton" = "Place a {amount} Bid";
|
||||
"GiftAuctionMinimumBid" = "minimum bid";
|
||||
"GiftAuctionUntilNextRound" = "until next round";
|
||||
"GiftAuctionLeft" = "left";
|
||||
"GiftAuctionTimeLeft" = "{time} left";
|
||||
"GiftAuctionYourBidWillBe" = "Your bid will be";
|
||||
"GiftAuctionYoureWinning" = "You're winning";
|
||||
"GiftAuctionTopWinners_one" = "Top {count} Winner";
|
||||
"GiftAuctionTopWinners_other" = "Top {count} Winners";
|
||||
"GiftAuctionAddToBid" = "Add {amount} to Your Bid";
|
||||
"GiftAuctionBalance" = "Balance";
|
||||
"GiftAuctionInfoTitle" = "Auction";
|
||||
"GiftAuctionInfoSubtitle" = "Join the battle for exclusive gifts.";
|
||||
"GiftAuctionInfoTopBiddersTitle_one" = "Top {count} Bidder";
|
||||
"GiftAuctionInfoTopBiddersTitle_other" = "Top {count} Bidders";
|
||||
"GiftAuctionInfoTopBiddersSubtitle_one" = "{count} gift is dropped at varying intervals to the top {count} bidder by bid amount.";
|
||||
"GiftAuctionInfoTopBiddersSubtitle_other" = "{count} gifts are dropped at varying intervals to the top {count} bidders by bid amount.";
|
||||
"GiftAuctionInfoBidCarryoverTitle" = "Bid Carryover";
|
||||
"GiftAuctionInfoBidCarryoverSubtitle" = "If your bid leaves the top {count}, it will automatically join the next round.";
|
||||
"GiftAuctionInfoMissedBiddersTitle" = "Missed Bidders";
|
||||
"GiftAuctionInfoMissedBiddersSubtitle" = "If your bid doesn't win after the final round, your Stars will be fully refunded.";
|
||||
"GiftAuctionItemsBought_one" = "{count} {gift} item bought >";
|
||||
"GiftAuctionItemsBought_other" = "{count} {gift} items bought >";
|
||||
"GiftAuctionBoughtGiftsTitle_one" = "{count} Bought Gift";
|
||||
"GiftAuctionBoughtGiftsTitle_other" = "{count} Bought Gifts";
|
||||
"GiftAuctionBoughtGiftHeader" = "{gift} #{giftNumber} in round {round}";
|
||||
"GiftAuctionRecipient" = "Recipient";
|
||||
"GiftAuctionDate" = "Date";
|
||||
"GiftAuctionAcceptedBid" = "Accepted Bid";
|
||||
"GiftAuctionTopPosition" = "TOP {position}";
|
||||
"GiftAuctionCustomBidTitle" = "Place a Custom Bid";
|
||||
"GiftAuctionCustomBidDescription" = "If you fall below the top {count}, your bid will roll over to the next round.";
|
||||
"GiftAuctionCustomBidPlaceholder" = "Custom Bid...";
|
||||
"GiftAuctionCustomBidButton" = "Place a Bid";
|
||||
"GiftAuctionBidPlacedTitle" = "Your bid has been placed";
|
||||
"GiftAuctionBidIncreasedTitle" = "Your bid has been increased";
|
||||
"GiftAuctionBidPlacedMessage" = "If you fall below the top {count}, your bid will roll over to the next round.";
|
||||
"GiftAuctionFinished" = "Finished";
|
||||
"GiftAuctionEnded" = "Auction ended";
|
||||
"GiftAuctionSoldOut" = "Sold Out!";
|
||||
"GiftAuctionGifts_one" = "{count} Gift";
|
||||
"GiftAuctionGifts_other" = "{count} Gifts";
|
||||
"GiftAuctionChangeRecipientTitle" = "Change Recipient";
|
||||
"GiftAuctionChangeRecipientDescription" = "You've already placed a bid on this gift for **{oldPeer}**. Do you want to raise your bid and change the recipient to **{newPeer}**?";
|
||||
"GiftAuctionAveragePrice" = "Average Price";
|
||||
"GiftAuctionTapToBidMore" = "click to bid more";
|
||||
"GiftAuctionWonNotification" = "You won {gift} at the auction!";
|
||||
"StarGift" = "Star Gift";
|
||||
"SettingsItemPrivacyPasskeys" = "Passkeys";
|
||||
"SettingsItemPrivacyOn" = "Enabled";
|
||||
"SettingsItemPrivacyOff" = "Disabled";
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
/* eslint-disable @stylistic/max-len */
|
||||
|
||||
export { default as StarsGiftModal } from '../components/modals/stars/gift/StarsGiftModal';
|
||||
export { default as StarsGiftingPickerModal } from '../components/main/premium/StarsGiftingPickerModal';
|
||||
export { default as StarsBalanceModal } from '../components/modals/stars/StarsBalanceModal';
|
||||
@ -12,6 +14,11 @@ export { default as GiftInfoValueModal } from '../components/modals/gift/value/G
|
||||
export { default as GiftLockedModal } from '../components/modals/gift/locked/GiftLockedModal';
|
||||
export { default as GiftResalePriceComposerModal } from '../components/modals/gift/resale/GiftResalePriceComposerModal';
|
||||
export { default as GiftUpgradeModal } from '../components/modals/gift/upgrade/GiftUpgradeModal';
|
||||
export { default as GiftAuctionModal } from '../components/modals/gift/auction/GiftAuctionModal';
|
||||
export { default as GiftAuctionBidModal } from '../components/modals/gift/auction/GiftAuctionBidModal';
|
||||
export { default as GiftAuctionInfoModal } from '../components/modals/gift/auction/GiftAuctionInfoModal';
|
||||
export { default as GiftAuctionAcquiredModal } from '../components/modals/gift/auction/GiftAuctionAcquiredModal';
|
||||
export { default as GiftAuctionChangeRecipientModal } from '../components/modals/gift/auction/GiftAuctionChangeRecipientModal';
|
||||
export { default as StarGiftPriceDecreaseInfoModal } from '../components/modals/gift/StarGiftPriceDecreaseInfoModal';
|
||||
export { default as GiftStatusInfoModal } from '../components/modals/gift/status/GiftStatusInfoModal';
|
||||
export { default as GiftWithdrawModal } from '../components/modals/gift/withdraw/GiftWithdrawModal';
|
||||
|
||||
@ -15,6 +15,12 @@ export type GiftAttributes = {
|
||||
backdrop?: ApiStarGiftAttributeBackdrop;
|
||||
};
|
||||
|
||||
export type GiftPreviewAttributes = {
|
||||
model: ApiStarGiftAttributeModel;
|
||||
pattern: ApiStarGiftAttributePattern;
|
||||
backdrop: ApiStarGiftAttributeBackdrop;
|
||||
};
|
||||
|
||||
export function getStickerFromGift(gift: ApiStarGift): ApiSticker | undefined {
|
||||
if (gift.type === 'starGift') {
|
||||
return gift.sticker;
|
||||
@ -52,3 +58,46 @@ function getGiftAttributesFromList(attributes: ApiStarGiftAttribute[]) {
|
||||
backdrop,
|
||||
};
|
||||
}
|
||||
|
||||
export function getRandomGiftPreviewAttributes(
|
||||
list: ApiStarGiftAttribute[],
|
||||
previousSelection?: GiftPreviewAttributes,
|
||||
): GiftPreviewAttributes {
|
||||
const models = list.filter((attr): attr is ApiStarGiftAttributeModel => (
|
||||
attr.type === 'model' && attr.name !== previousSelection?.model.name
|
||||
));
|
||||
const patterns = list.filter((attr): attr is ApiStarGiftAttributePattern => (
|
||||
attr.type === 'pattern' && attr.name !== previousSelection?.pattern.name
|
||||
));
|
||||
const backdrops = list.filter((attr): attr is ApiStarGiftAttributeBackdrop => (
|
||||
attr.type === 'backdrop' && attr.name !== previousSelection?.backdrop.name
|
||||
));
|
||||
|
||||
if (!models.length || !patterns.length || !backdrops.length) {
|
||||
// Fallback: re-filter without exclusions if any category is empty
|
||||
const fallbackModels = models.length ? models
|
||||
: list.filter((attr): attr is ApiStarGiftAttributeModel => attr.type === 'model');
|
||||
|
||||
const fallbackPatterns = patterns.length ? patterns
|
||||
: list.filter((attr): attr is ApiStarGiftAttributePattern => attr.type === 'pattern');
|
||||
|
||||
const fallbackBackdrops = backdrops.length ? backdrops
|
||||
: list.filter((attr): attr is ApiStarGiftAttributeBackdrop => attr.type === 'backdrop');
|
||||
|
||||
return {
|
||||
model: fallbackModels[Math.floor(Math.random() * fallbackModels.length)],
|
||||
pattern: fallbackPatterns[Math.floor(Math.random() * fallbackPatterns.length)],
|
||||
backdrop: fallbackBackdrops[Math.floor(Math.random() * fallbackBackdrops.length)],
|
||||
};
|
||||
}
|
||||
|
||||
const randomModel = models[Math.floor(Math.random() * models.length)];
|
||||
const randomPattern = patterns[Math.floor(Math.random() * patterns.length)];
|
||||
const randomBackdrop = backdrops[Math.floor(Math.random() * backdrops.length)];
|
||||
|
||||
return {
|
||||
model: randomModel,
|
||||
pattern: randomPattern,
|
||||
backdrop: randomBackdrop,
|
||||
};
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ const PickerModal = ({
|
||||
containerRef: dialogRef,
|
||||
selector: `.modal-content ${itemsContainerSelector}`,
|
||||
isBottomNotch: true,
|
||||
shouldHideTopNotch: true,
|
||||
}, [modalProps.isOpen]);
|
||||
|
||||
return (
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
import { addExtraClass } from '../../lib/teact/teact-dom';
|
||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||
|
||||
import type { ApiChatFolder, ApiLimitTypeWithModal, ApiUser } from '../../api/types';
|
||||
import type { ApiChatFolder, ApiLimitTypeWithModal, ApiStarGiftAuctionState, ApiUser } from '../../api/types';
|
||||
import type { TabState } from '../../global/types';
|
||||
|
||||
import { BASE_EMOJI_KEYWORD_LANG, DEBUG, FOLDERS_POSITION_LEFT, INACTIVE_MARKER } from '../../config';
|
||||
@ -145,6 +145,7 @@ type StateProps = {
|
||||
isAccountFrozen?: boolean;
|
||||
isAppConfigLoaded?: boolean;
|
||||
isFoldersSidebarShown: boolean;
|
||||
activeGiftAuction?: ApiStarGiftAuctionState;
|
||||
};
|
||||
|
||||
const APP_OUTDATED_TIMEOUT_MS = 5 * 60 * 1000; // 5 min
|
||||
@ -198,6 +199,7 @@ const Main = ({
|
||||
isAccountFrozen,
|
||||
isAppConfigLoaded,
|
||||
isFoldersSidebarShown,
|
||||
activeGiftAuction,
|
||||
}: OwnProps & StateProps) => {
|
||||
const {
|
||||
initMain,
|
||||
@ -258,6 +260,7 @@ const Main = ({
|
||||
loadAllStories,
|
||||
loadAllHiddenStories,
|
||||
loadContentSettings,
|
||||
loadActiveGiftAuction,
|
||||
loadPromoData,
|
||||
} = getActions();
|
||||
|
||||
@ -439,6 +442,15 @@ const Main = ({
|
||||
});
|
||||
}, [currentUserId]);
|
||||
|
||||
// Refresh active gift auction subscription
|
||||
const auctionTimeout = activeGiftAuction?.timeout;
|
||||
const auctionGiftId = activeGiftAuction?.gift.id;
|
||||
useInterval(() => {
|
||||
if (auctionGiftId) {
|
||||
loadActiveGiftAuction({ giftId: auctionGiftId });
|
||||
}
|
||||
}, auctionTimeout ? auctionTimeout * 1000 : undefined);
|
||||
|
||||
// Restore Transition slide class after async rendering
|
||||
useLayoutEffect(() => {
|
||||
const container = containerRef.current!;
|
||||
@ -635,6 +647,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
payment,
|
||||
limitReachedModal,
|
||||
deleteFolderDialogModal,
|
||||
activeGiftAuction,
|
||||
} = selectTabState(global);
|
||||
|
||||
const { wasTimeFormatSetManually, foldersPosition } = selectSharedSettings(global);
|
||||
@ -693,6 +706,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
isAccountFrozen,
|
||||
isAppConfigLoaded: global.isAppConfigLoaded,
|
||||
isFoldersSidebarShown: foldersPosition === FOLDERS_POSITION_LEFT && !isMobile && selectAreFoldersPresent(global),
|
||||
activeGiftAuction,
|
||||
};
|
||||
},
|
||||
)(Main));
|
||||
|
||||
@ -556,7 +556,7 @@ const ActionMessageText = ({
|
||||
|
||||
case 'starGift': {
|
||||
const {
|
||||
gift, alreadyPaidUpgradeStars, peerId, savedId, fromId, isPrepaidUpgrade,
|
||||
gift, alreadyPaidUpgradeStars, peerId, savedId, fromId, isPrepaidUpgrade, isAuctionAcquired,
|
||||
} = action;
|
||||
const isToChannel = Boolean(peerId && savedId);
|
||||
|
||||
@ -572,6 +572,10 @@ const ActionMessageText = ({
|
||||
const starsAmount = gift.stars + (alreadyPaidUpgradeStars || 0);
|
||||
const cost = renderStrong(formatStarsAsText(lang, starsAmount));
|
||||
|
||||
if (isAuctionAcquired) {
|
||||
return lang('ActionStarGiftAuctionWon', { cost }, { withNodes: true });
|
||||
}
|
||||
|
||||
if (isPrepaidUpgrade && gift.upgradeStars) {
|
||||
const upgradeCost = renderStrong(formatStarsAsText(lang, gift.upgradeStars));
|
||||
|
||||
|
||||
@ -72,6 +72,11 @@
|
||||
opacity: 0.85;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-inline-end: 0.25rem;
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.with-gift &--quick-button {
|
||||
|
||||
@ -13,7 +13,7 @@ import buildClassName from '../../../util/buildClassName';
|
||||
import { tryParseDeepLink } from '../../../util/deepLinkParser';
|
||||
import trimText from '../../../util/trimText';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
import { getWebpageButtonLangKey } from './helpers/webpageType';
|
||||
import { getWebpageButtonIcon, getWebpageButtonLangKey } from './helpers/webpageType';
|
||||
|
||||
import useDynamicColorListener from '../../../hooks/stickers/useDynamicColorListener';
|
||||
import useEnsureStory from '../../../hooks/useEnsureStory';
|
||||
@ -23,6 +23,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import Audio from '../../common/Audio';
|
||||
import Document from '../../common/Document';
|
||||
import EmojiIconBackground from '../../common/embedded/EmojiIconBackground';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import PeerColorWrapper from '../../common/PeerColorWrapper';
|
||||
import SafeLink from '../../common/SafeLink';
|
||||
import StickerView from '../../common/StickerView';
|
||||
@ -30,6 +31,7 @@ import Button from '../../ui/Button';
|
||||
import BaseStory from './BaseStory';
|
||||
import Photo from './Photo';
|
||||
import Video from './Video';
|
||||
import WebPageStarGiftAuction from './WebPageStarGiftAuction';
|
||||
import WebPageUniqueGift from './WebPageUniqueGift';
|
||||
|
||||
import './WebPage.scss';
|
||||
@ -37,6 +39,7 @@ import './WebPage.scss';
|
||||
const MAX_TEXT_LENGTH = 170; // symbols
|
||||
const WEBPAGE_STORY_TYPE = 'telegram_story';
|
||||
const WEBPAGE_GIFT_TYPE = 'telegram_nft';
|
||||
const WEBPAGE_AUCTION_TYPE = 'telegram_auction';
|
||||
const STICKER_SIZE = 80;
|
||||
const EMOJI_SIZE = 38;
|
||||
|
||||
@ -145,11 +148,14 @@ const WebPage: FC<OwnProps & StateProps> = ({
|
||||
const { mediaSize } = messageWebPage;
|
||||
const isStory = type === WEBPAGE_STORY_TYPE;
|
||||
const isGift = type === WEBPAGE_GIFT_TYPE;
|
||||
const isAuction = type === WEBPAGE_AUCTION_TYPE;
|
||||
const isExpiredStory = story && 'isDeleted' in story;
|
||||
|
||||
const resultType = stickers?.isEmoji ? 'telegram_emojiset' : type;
|
||||
const quickButtonLangKey = !isExpiredStory ? getWebpageButtonLangKey(resultType) : undefined;
|
||||
const auctionEndDate = isAuction && webPage.auction ? webPage.auction.endDate : undefined;
|
||||
const quickButtonLangKey = !isExpiredStory ? getWebpageButtonLangKey(resultType, auctionEndDate) : undefined;
|
||||
const quickButtonTitle = quickButtonLangKey && lang(quickButtonLangKey);
|
||||
const quickButtonIcon = getWebpageButtonIcon(resultType);
|
||||
|
||||
const truncatedDescription = trimText(description, MAX_TEXT_LENGTH);
|
||||
const isArticle = Boolean(truncatedDescription || title || siteName);
|
||||
@ -167,20 +173,21 @@ const WebPage: FC<OwnProps & StateProps> = ({
|
||||
!isArticle && 'no-article',
|
||||
document && 'with-document',
|
||||
quickButtonTitle && 'with-quick-button',
|
||||
isGift && 'with-gift',
|
||||
(isGift || isAuction) && 'with-gift',
|
||||
);
|
||||
|
||||
function renderQuickButton(caption: string) {
|
||||
function renderQuickButton() {
|
||||
return (
|
||||
<Button
|
||||
className="WebPage--quick-button"
|
||||
size="tiny"
|
||||
color="translucent"
|
||||
isRectangular
|
||||
noForcedUpperCase
|
||||
noForcedUpperCase={!isAuction}
|
||||
onClick={handleOpenTelegramLink}
|
||||
>
|
||||
{caption}
|
||||
{quickButtonIcon && <Icon name={quickButtonIcon} />}
|
||||
{quickButtonTitle}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@ -195,7 +202,7 @@ const WebPage: FC<OwnProps & StateProps> = ({
|
||||
<div className={buildClassName(
|
||||
'WebPage--content',
|
||||
isStory && 'is-story',
|
||||
isGift && 'is-gift',
|
||||
(isGift || isAuction) && 'is-gift',
|
||||
)}
|
||||
>
|
||||
{backgroundEmojiId && (
|
||||
@ -215,6 +222,14 @@ const WebPage: FC<OwnProps & StateProps> = ({
|
||||
onClick={handleOpenTelegramLink}
|
||||
/>
|
||||
)}
|
||||
{isAuction && webPage.auction && (
|
||||
<WebPageStarGiftAuction
|
||||
auction={webPage.auction}
|
||||
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||
onClick={handleOpenTelegramLink}
|
||||
/>
|
||||
)}
|
||||
{isArticle && (
|
||||
<div
|
||||
className={buildClassName('WebPage-text', 'WebPage-text_interactive')}
|
||||
@ -224,12 +239,12 @@ const WebPage: FC<OwnProps & StateProps> = ({
|
||||
{title && (
|
||||
<p className="site-title">{renderText(title)}</p>
|
||||
)}
|
||||
{truncatedDescription && !isGift && (
|
||||
{truncatedDescription && !isGift && !isAuction && (
|
||||
<p className="site-description">{renderText(truncatedDescription, ['emoji', 'br'])}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{photo && !isGift && !video && !document && (
|
||||
{photo && !isGift && !isAuction && !video && !document && (
|
||||
<Photo
|
||||
photo={photo}
|
||||
isOwn={message?.isOutgoing}
|
||||
@ -310,7 +325,7 @@ const WebPage: FC<OwnProps & StateProps> = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{quickButtonTitle && renderQuickButton(quickButtonTitle)}
|
||||
{quickButtonTitle && renderQuickButton()}
|
||||
</PeerColorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
.root {
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
min-width: 12.5rem;
|
||||
margin-top: 0;
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 0.75rem;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.stickerWrapper {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
width: 7.5rem;
|
||||
height: 7.5rem;
|
||||
margin-block: 0.5rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
font-size: 0.9375rem;
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
font-size: 0.8125rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 1rem;
|
||||
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 0.5rem;
|
||||
left: 0.5rem;
|
||||
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 1rem;
|
||||
|
||||
font-size: 0.6875rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 0.75rem;
|
||||
|
||||
backdrop-filter: blur(0.5rem);
|
||||
}
|
||||
103
src/components/middle/message/WebPageStarGiftAuction.tsx
Normal file
103
src/components/middle/message/WebPageStarGiftAuction.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import { memo, useMemo, useRef, useState } from '../../../lib/teact/teact';
|
||||
|
||||
import type { ApiWebPageAuctionData } from '../../../api/types';
|
||||
|
||||
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import { type ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import GiftEffectWrapper from '../../common/gift/GiftEffectWrapper';
|
||||
import RadialPatternBackground from '../../common/profile/RadialPatternBackground';
|
||||
import StickerView from '../../common/StickerView';
|
||||
import TextTimer from '../../ui/TextTimer';
|
||||
|
||||
import styles from './WebPageStarGiftAuction.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
auction: ApiWebPageAuctionData;
|
||||
observeIntersectionForLoading?: ObserveFn;
|
||||
observeIntersectionForPlaying?: ObserveFn;
|
||||
onClick?: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
const GIFT_STICKER_SIZE = 120;
|
||||
const DEFAULT_CENTER_COLOR = '#254e7a';
|
||||
const DEFAULT_EDGE_COLOR = '#0f2a49';
|
||||
|
||||
const WebPageStarGiftAuction = ({
|
||||
auction,
|
||||
observeIntersectionForLoading,
|
||||
observeIntersectionForPlaying,
|
||||
onClick,
|
||||
}: OwnProps) => {
|
||||
const lang = useLang();
|
||||
|
||||
const stickerRef = useRef<HTMLDivElement>();
|
||||
const [isHover, markHover, unmarkHover] = useFlag();
|
||||
|
||||
const { gift, endDate } = auction;
|
||||
const { background, title, availabilityTotal, isSoldOut } = gift;
|
||||
const textColor = background?.textColor || '#ffffff';
|
||||
|
||||
const [isFinished, setIsFinished] = useState(() => endDate < getServerTime());
|
||||
|
||||
const handleTimerEnd = useLastCallback(() => {
|
||||
setIsFinished(true);
|
||||
});
|
||||
|
||||
const backgroundColors = useMemo(() => {
|
||||
const centerColor = background?.centerColor || DEFAULT_CENTER_COLOR;
|
||||
const edgeColor = background?.edgeColor || DEFAULT_EDGE_COLOR;
|
||||
return [centerColor, edgeColor];
|
||||
}, [background]);
|
||||
|
||||
const subtitleText = useMemo(() => {
|
||||
if (isFinished || isSoldOut) {
|
||||
return lang('GiftAuctionSoldOut');
|
||||
}
|
||||
return lang('GiftAuctionGifts', { count: availabilityTotal || 0 }, { pluralValue: availabilityTotal || 0 });
|
||||
}, [availabilityTotal, isFinished, isSoldOut, lang]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={buildClassName('interactive-gift', styles.root)}
|
||||
style={`color: ${textColor}`}
|
||||
onClick={onClick}
|
||||
onMouseEnter={!IS_TOUCH_ENV ? markHover : undefined}
|
||||
onMouseLeave={!IS_TOUCH_ENV ? unmarkHover : undefined}
|
||||
>
|
||||
<RadialPatternBackground
|
||||
className={styles.background}
|
||||
backgroundColors={backgroundColors}
|
||||
withAdaptiveHeight
|
||||
/>
|
||||
<div className={styles.badge} style={`background-color: ${backgroundColors[0]}`}>
|
||||
{isFinished ? lang('GiftAuctionFinished') : <TextTimer endsAt={endDate} onEnd={handleTimerEnd} />}
|
||||
</div>
|
||||
<GiftEffectWrapper
|
||||
ref={stickerRef}
|
||||
className={styles.stickerWrapper}
|
||||
withSparkles
|
||||
sparklesColor={textColor}
|
||||
>
|
||||
<StickerView
|
||||
containerRef={stickerRef}
|
||||
sticker={gift.sticker}
|
||||
size={GIFT_STICKER_SIZE}
|
||||
shouldLoop={isHover}
|
||||
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||
/>
|
||||
</GiftEffectWrapper>
|
||||
<div className={styles.title}>{title}</div>
|
||||
<div className={styles.subtitle}>{subtitleText}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(WebPageStarGiftAuction);
|
||||
@ -27,7 +27,8 @@
|
||||
.message-content {
|
||||
position: relative;
|
||||
max-width: var(--max-width);
|
||||
&.gift {
|
||||
&.gift,
|
||||
&.auction {
|
||||
--max-width: 18rem;
|
||||
}
|
||||
|
||||
|
||||
@ -72,23 +72,31 @@ const StarGiftAction = ({
|
||||
|
||||
const peer = isOutgoing ? recipient : sender;
|
||||
const isChannel = peer && isApiPeerChat(peer) && isChatChannel(peer);
|
||||
const isAuction = action.isAuctionAcquired;
|
||||
|
||||
const backgroundColor = useDynamicColorListener(ref, 'background-color', !action.gift.availabilityTotal);
|
||||
|
||||
const fallbackPeerTitle = lang('ActionFallbackSomeone');
|
||||
const peerTitle = peer && getPeerTitle(lang, peer);
|
||||
const auctionToTitle = recipient && getPeerTitle(lang, recipient);
|
||||
const isSelf = sender?.id === recipient?.id;
|
||||
|
||||
const auctionBid = isAuction ? action.gift.stars : undefined;
|
||||
|
||||
const giftDescription = useMemo(() => {
|
||||
const peerLink = renderPeerLink(peer?.id, peerTitle || fallbackPeerTitle);
|
||||
const starsAmount = action.starsToConvert !== undefined
|
||||
? formatStarsAsText(lang, action.starsToConvert) : undefined;
|
||||
|
||||
if (isAuction && auctionBid !== undefined) {
|
||||
return lang('ActionStarGiftAuctionBought', { cost: formatStarsAsText(lang, auctionBid) });
|
||||
}
|
||||
|
||||
if (action.isUpgraded) {
|
||||
return lang('ActionStarGiftUpgraded');
|
||||
}
|
||||
|
||||
if (action.alreadyPaidUpgradeStars) {
|
||||
if (action.alreadyPaidUpgradeStars && !isAuction) {
|
||||
return translateWithYou(
|
||||
lang, 'ActionStarGiftUpgradeText', !isOutgoing || isSelf, { peer: peerLink },
|
||||
);
|
||||
@ -100,7 +108,7 @@ const StarGiftAction = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (starGiftMaxConvertPeriod && getServerTime() < message.date + starGiftMaxConvertPeriod) {
|
||||
if (starGiftMaxConvertPeriod && getServerTime() < message.date + starGiftMaxConvertPeriod && starsAmount) {
|
||||
return translateWithYou(
|
||||
lang, 'ActionStarGiftConvertText', !isOutgoing || isSelf, { peer: peerLink, amount: starsAmount },
|
||||
);
|
||||
@ -116,8 +124,8 @@ const StarGiftAction = ({
|
||||
lang, 'ActionStarGiftNoConvertText', !isOutgoing || isSelf, { peer: peerLink },
|
||||
);
|
||||
}, [
|
||||
action, fallbackPeerTitle, isChannel, isOutgoing, lang, message.date, peer?.id, peerTitle, starGiftMaxConvertPeriod,
|
||||
isSelf,
|
||||
action, auctionBid, fallbackPeerTitle, isAuction, isChannel, isOutgoing, lang, message.date, peer?.id, peerTitle,
|
||||
starGiftMaxConvertPeriod, isSelf,
|
||||
]);
|
||||
|
||||
return (
|
||||
@ -157,7 +165,11 @@ const StarGiftAction = ({
|
||||
)}
|
||||
<div className={styles.info}>
|
||||
<h3 className={styles.title}>
|
||||
{isSelf ? lang('ActionStarGiftSelf') : lang(
|
||||
{isAuction && recipient ? lang(
|
||||
'ActionStarGiftAuctionFor',
|
||||
{ peer: renderPeerLink(recipient.id, auctionToTitle || fallbackPeerTitle) },
|
||||
{ withNodes: true },
|
||||
) : isSelf ? lang('ActionStarGiftSelf') : lang(
|
||||
isOutgoing ? 'ActionStarGiftTo' : 'ActionStarGiftFrom',
|
||||
{
|
||||
peer: renderPeerLink(peer?.id, peerTitle || fallbackPeerTitle),
|
||||
@ -188,7 +200,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
const messageSender = selectSender(global, message);
|
||||
const giftSender = action.fromId ? selectPeer(global, action.fromId) : undefined;
|
||||
const messageRecipient = message.isOutgoing ? selectPeer(global, message.chatId) : currentUser;
|
||||
const giftRecipient = action.peerId ? selectPeer(global, action.peerId) : undefined;
|
||||
const giftRecipientId = action.toId || action.peerId;
|
||||
const giftRecipient = giftRecipientId ? selectPeer(global, giftRecipientId) : undefined;
|
||||
|
||||
return {
|
||||
canPlayAnimatedEmojis,
|
||||
|
||||
@ -144,6 +144,10 @@ export function buildContentClassName(
|
||||
if (webPage.gift) {
|
||||
classNames.push('gift');
|
||||
}
|
||||
|
||||
if (webPage.auction) {
|
||||
classNames.push('auction');
|
||||
}
|
||||
}
|
||||
|
||||
if (invoice && !invoice.extendedMedia) {
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import type { IconName } from '../../../../types/icons';
|
||||
import type { RegularLangKey } from '../../../../types/language';
|
||||
|
||||
import { getServerTime } from '../../../../util/serverTime';
|
||||
|
||||
// https://github.com/telegramdesktop/tdesktop/blob/3da787791f6d227f69b32bf4003bc6071d05e2ac/Telegram/SourceFiles/history/view/history_view_view_button.cpp#L51
|
||||
export function getWebpageButtonLangKey(type?: string): RegularLangKey | undefined {
|
||||
export function getWebpageButtonLangKey(type?: string, auctionEndDate?: number): RegularLangKey | undefined {
|
||||
switch (type) {
|
||||
case 'telegram_channel_request':
|
||||
case 'telegram_megagroup_request':
|
||||
@ -37,7 +40,18 @@ export function getWebpageButtonLangKey(type?: string): RegularLangKey | undefin
|
||||
return 'ViewButtonEmojiset';
|
||||
case 'telegram_nft':
|
||||
return 'ViewButtonGiftUnique';
|
||||
case 'telegram_auction': {
|
||||
const isFinished = auctionEndDate !== undefined && auctionEndDate * 1000 < getServerTime();
|
||||
return isFinished ? 'PollViewResults' : 'GiftAuctionJoin';
|
||||
}
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function getWebpageButtonIcon(type?: string): IconName | undefined {
|
||||
if (type === 'telegram_auction') {
|
||||
return 'auction-filled';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -19,6 +19,11 @@ import CollectibleInfoModal from './collectible/CollectibleInfoModal.async';
|
||||
import DeleteAccountModal from './deleteAccount/DeleteAccountModal.async';
|
||||
import EmojiStatusAccessModal from './emojiStatusAccess/EmojiStatusAccessModal.async';
|
||||
import FrozenAccountModal from './frozenAccount/FrozenAccountModal.async';
|
||||
import GiftAuctionAcquiredModal from './gift/auction/GiftAuctionAcquiredModal.async';
|
||||
import GiftAuctionBidModal from './gift/auction/GiftAuctionBidModal.async';
|
||||
import GiftAuctionChangeRecipientModal from './gift/auction/GiftAuctionChangeRecipientModal.async';
|
||||
import GiftAuctionInfoModal from './gift/auction/GiftAuctionInfoModal.async';
|
||||
import GiftAuctionModal from './gift/auction/GiftAuctionModal.async';
|
||||
import PremiumGiftModal from './gift/GiftModal.async';
|
||||
import GiftInfoModal from './gift/info/GiftInfoModal.async';
|
||||
import GiftLockedModal from './gift/locked/GiftLockedModal.async';
|
||||
@ -94,6 +99,11 @@ type ModalKey = keyof Pick<TabState,
|
||||
'locationAccessModal' |
|
||||
'aboutAdsModal' |
|
||||
'giftUpgradeModal' |
|
||||
'giftAuctionModal' |
|
||||
'giftAuctionBidModal' |
|
||||
'giftAuctionInfoModal' |
|
||||
'giftAuctionChangeRecipientModal' |
|
||||
'giftAuctionAcquiredModal' |
|
||||
'starGiftPriceDecreaseInfoModal' |
|
||||
'monetizationVerificationModal' |
|
||||
'giftWithdrawModal' |
|
||||
@ -161,6 +171,11 @@ const MODALS: ModalRegistry = {
|
||||
locationAccessModal: LocationAccessModal,
|
||||
aboutAdsModal: AboutAdsModal,
|
||||
giftUpgradeModal: GiftUpgradeModal,
|
||||
giftAuctionModal: GiftAuctionModal,
|
||||
giftAuctionBidModal: GiftAuctionBidModal,
|
||||
giftAuctionInfoModal: GiftAuctionInfoModal,
|
||||
giftAuctionChangeRecipientModal: GiftAuctionChangeRecipientModal,
|
||||
giftAuctionAcquiredModal: GiftAuctionAcquiredModal,
|
||||
starGiftPriceDecreaseInfoModal: StarGiftPriceDecreaseInfoModal,
|
||||
monetizationVerificationModal: VerificationMonetizationModal,
|
||||
giftWithdrawModal: GiftWithdrawModal,
|
||||
|
||||
44
src/components/modals/common/TableInfo.module.scss
Normal file
44
src/components/modals/common/TableInfo.module.scss
Normal file
@ -0,0 +1,44 @@
|
||||
.table {
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
flex-shrink: 0;
|
||||
gap: 1px;
|
||||
|
||||
border: 1px solid var(--color-borders);
|
||||
border-radius: 1rem;
|
||||
|
||||
background-color: var(--color-borders);
|
||||
}
|
||||
|
||||
.cell {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
min-height: 2.5rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: var(--color-background-secondary);
|
||||
}
|
||||
|
||||
.value {
|
||||
min-width: 2rem;
|
||||
overflow-wrap: anywhere;
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.fullWidth {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.chatItem {
|
||||
width: fit-content;
|
||||
color: var(--color-primary);
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
64
src/components/modals/common/TableInfo.tsx
Normal file
64
src/components/modals/common/TableInfo.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import { memo, type TeactNode } from '../../../lib/teact/teact';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import PeerChip from '../../common/PeerChip';
|
||||
|
||||
import styles from './TableInfo.module.scss';
|
||||
|
||||
type ChatItem = { chatId: string; withEmojiStatus?: boolean };
|
||||
|
||||
export type TableData = [TeactNode | undefined, TeactNode | ChatItem][];
|
||||
|
||||
type OwnProps = {
|
||||
tableData?: TableData;
|
||||
className?: string;
|
||||
onChatClick?: (peerId: string) => void;
|
||||
};
|
||||
|
||||
const TableInfo = ({
|
||||
tableData,
|
||||
className,
|
||||
onChatClick,
|
||||
}: OwnProps) => {
|
||||
const { openChat } = getActions();
|
||||
|
||||
const handleOpenChat = useLastCallback((peerId: string) => {
|
||||
if (onChatClick) {
|
||||
onChatClick(peerId);
|
||||
} else {
|
||||
openChat({ id: peerId });
|
||||
}
|
||||
});
|
||||
|
||||
if (!tableData?.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={buildClassName(styles.table, className)}>
|
||||
{tableData.map(([label, value]) => (
|
||||
<>
|
||||
{Boolean(label) && <div className={buildClassName(styles.cell, styles.title)}>{label}</div>}
|
||||
<div className={buildClassName(styles.cell, styles.value, !label && styles.fullWidth)}>
|
||||
{typeof value === 'object' && 'chatId' in value ? (
|
||||
<PeerChip
|
||||
peerId={value.chatId}
|
||||
className={styles.chatItem}
|
||||
forceShowSelf
|
||||
withEmojiStatus={value.withEmojiStatus}
|
||||
clickArg={value.chatId}
|
||||
onClick={handleOpenChat}
|
||||
/>
|
||||
) : value}
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(TableInfo);
|
||||
@ -1,5 +1,3 @@
|
||||
@use '../../../styles/mixins';
|
||||
|
||||
.content {
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
@ -10,55 +8,10 @@
|
||||
padding-inline: 1rem !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: var(--color-background-secondary);
|
||||
}
|
||||
|
||||
.value {
|
||||
min-width: 2rem;
|
||||
overflow-wrap: anywhere;
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.table {
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
flex-shrink: 0;
|
||||
gap: 1px;
|
||||
|
||||
border: 1px solid var(--color-borders);
|
||||
border-radius: 1rem;
|
||||
|
||||
background-color: var(--color-borders);
|
||||
}
|
||||
|
||||
.noFooter {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.cell {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
min-height: 2.5rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.fullWidth {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.chatItem {
|
||||
width: fit-content;
|
||||
color: var(--color-primary);
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
@ -9,15 +9,13 @@ import buildClassName from '../../../util/buildClassName';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Avatar from '../../common/Avatar';
|
||||
import PeerChip from '../../common/PeerChip';
|
||||
import Button from '../../ui/Button';
|
||||
import Modal from '../../ui/Modal';
|
||||
import TableInfo, { type TableData } from './TableInfo';
|
||||
|
||||
import styles from './TableInfoModal.module.scss';
|
||||
|
||||
type ChatItem = { chatId: string; withEmojiStatus?: boolean };
|
||||
|
||||
export type TableData = [TeactNode | undefined, TeactNode | ChatItem][];
|
||||
export type { TableData };
|
||||
|
||||
type OwnProps = {
|
||||
isOpen?: boolean;
|
||||
@ -59,7 +57,8 @@ const TableInfoModal = ({
|
||||
currencyInBalanceBar,
|
||||
}: OwnProps) => {
|
||||
const { openChat } = getActions();
|
||||
const handleOpenChat = useLastCallback((peerId: string) => {
|
||||
|
||||
const handleChatClick = useLastCallback((peerId: string) => {
|
||||
openChat({ id: peerId });
|
||||
onClose();
|
||||
});
|
||||
@ -84,25 +83,7 @@ const TableInfoModal = ({
|
||||
<Avatar peer={headerAvatarPeer} size="jumbo" className={styles.avatar} />
|
||||
)}
|
||||
{header}
|
||||
<div className={buildClassName(styles.table, tableClassName)}>
|
||||
{tableData?.map(([label, value]) => (
|
||||
<>
|
||||
{Boolean(label) && <div className={buildClassName(styles.cell, styles.title)}>{label}</div>}
|
||||
<div className={buildClassName(styles.cell, styles.value, !label && styles.fullWidth)}>
|
||||
{typeof value === 'object' && 'chatId' in value ? (
|
||||
<PeerChip
|
||||
peerId={value.chatId}
|
||||
className={styles.chatItem}
|
||||
forceShowSelf
|
||||
withEmojiStatus={value.withEmojiStatus}
|
||||
clickArg={value.chatId}
|
||||
onClick={handleOpenChat}
|
||||
/>
|
||||
) : value}
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
<TableInfo tableData={tableData} className={tableClassName} onChatClick={handleChatClick} />
|
||||
{footer}
|
||||
{buttonText && (
|
||||
<Button
|
||||
|
||||
@ -144,6 +144,7 @@
|
||||
margin-inline-end: 0.125rem !important;
|
||||
}
|
||||
|
||||
.bottomDescription,
|
||||
.description {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 1rem;
|
||||
@ -151,12 +152,23 @@
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.bottomDescription {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.main-button {
|
||||
display: flex;
|
||||
font-size: 1rem;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.buttonSubtitle {
|
||||
display: block;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: var(--font-weight-normal);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.star {
|
||||
--color-fill: var(--color-white);
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import { getActions, withGlobal } from '../../../global';
|
||||
import type { ThemeKey } from '../../../types';
|
||||
import type { GiftOption } from './GiftModal';
|
||||
import {
|
||||
type ApiMessage, type ApiPeer, type ApiStarsAmount, MAIN_THREAD_ID,
|
||||
type ApiMessage, type ApiPeer, type ApiStarGiftAuctionState, type ApiStarsAmount, MAIN_THREAD_ID,
|
||||
} from '../../../api/types';
|
||||
|
||||
import { getPeerTitle, isApiPeerUser } from '../../../global/helpers/peers';
|
||||
@ -16,8 +16,11 @@ import {
|
||||
} from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import buildStyle from '../../../util/buildStyle';
|
||||
import { formatCountdown } from '../../../util/dates/dateFormat';
|
||||
import { HOUR } from '../../../util/dates/units';
|
||||
import { formatCurrency } from '../../../util/formatCurrency';
|
||||
import { formatStarsAsIcon } from '../../../util/localization/format';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
|
||||
import useCustomBackground from '../../../hooks/useCustomBackground';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
@ -30,6 +33,7 @@ import Link from '../../ui/Link';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Switcher from '../../ui/Switcher';
|
||||
import TextArea from '../../ui/TextArea';
|
||||
import TextTimer from '../../ui/TextTimer';
|
||||
|
||||
import styles from './GiftComposer.module.scss';
|
||||
|
||||
@ -53,9 +57,11 @@ export type StateProps = {
|
||||
paidMessagesStars?: number;
|
||||
areUniqueStarGiftsDisallowed?: boolean;
|
||||
shouldDisallowLimitedStarGifts?: boolean;
|
||||
activeGiftAuction?: ApiStarGiftAuctionState;
|
||||
};
|
||||
|
||||
const LIMIT_DISPLAY_THRESHOLD = 50;
|
||||
const TEXT_TIMER_THRESHOLD = 48 * HOUR;
|
||||
|
||||
function GiftComposer({
|
||||
gift,
|
||||
@ -74,9 +80,11 @@ function GiftComposer({
|
||||
paidMessagesStars,
|
||||
areUniqueStarGiftsDisallowed,
|
||||
shouldDisallowLimitedStarGifts,
|
||||
activeGiftAuction,
|
||||
}: OwnProps & StateProps) {
|
||||
const {
|
||||
sendStarGift, sendPremiumGiftByStars, openInvoice, openGiftUpgradeModal, openStarsBalanceModal,
|
||||
openGiftAuctionBidModal, openGiftAuctionInfoModal, openGiftAuctionChangeRecipientModal,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
@ -180,7 +188,31 @@ function GiftComposer({
|
||||
openStarsBalanceModal({});
|
||||
});
|
||||
|
||||
const handleLearnMoreClick = useLastCallback(() => {
|
||||
openGiftAuctionInfoModal({});
|
||||
});
|
||||
|
||||
const handleMainButtonClick = useLastCallback(() => {
|
||||
if (activeGiftAuction) {
|
||||
const existingBidPeerId = activeGiftAuction.userState.bidPeerId;
|
||||
if (existingBidPeerId && existingBidPeerId !== peerId) {
|
||||
openGiftAuctionChangeRecipientModal({
|
||||
oldPeerId: existingBidPeerId,
|
||||
newPeerId: peerId,
|
||||
message: giftMessage || undefined,
|
||||
shouldHideName: shouldHideName || undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
openGiftAuctionBidModal({
|
||||
peerId,
|
||||
message: giftMessage || undefined,
|
||||
shouldHideName: shouldHideName || undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isStarGift) {
|
||||
sendStarGift({
|
||||
peerId,
|
||||
@ -328,6 +360,12 @@ function GiftComposer({
|
||||
? formatStarsAsIcon(lang, gift.stars + (shouldPayForUpgrade ? gift.upgradeStars! : 0), { asFont: true })
|
||||
: isPremiumGift ? formatCurrency(lang, gift.amount, gift.currency) : undefined;
|
||||
|
||||
const giftsPerRound = activeGiftAuction?.gift.giftsPerRound;
|
||||
const auctionEndDate = activeGiftAuction?.state.endDate;
|
||||
const auctionTimeLeft = auctionEndDate ? auctionEndDate - getServerTime() : undefined;
|
||||
const shouldUseTextTimer = auctionTimeLeft !== undefined && auctionTimeLeft > 0
|
||||
&& auctionTimeLeft < TEXT_TIMER_THRESHOLD;
|
||||
|
||||
return (
|
||||
<div className={styles.footer}>
|
||||
{isStarGift && Boolean(gift.availabilityRemains) && (
|
||||
@ -341,13 +379,37 @@ function GiftComposer({
|
||||
className={styles.limited}
|
||||
/>
|
||||
)}
|
||||
{activeGiftAuction && Boolean(giftsPerRound) && (
|
||||
<div className={styles.bottomDescription}>
|
||||
{lang('GiftAuctionDescription', {
|
||||
count: giftsPerRound,
|
||||
link: <Link isPrimary onClick={handleLearnMoreClick}>{lang('GiftAuctionLearnMore')}</Link>,
|
||||
}, { pluralValue: giftsPerRound, withNodes: true })}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
className={styles.mainButton}
|
||||
size="smaller"
|
||||
size={auctionTimeLeft ? undefined : 'smaller'}
|
||||
onClick={handleMainButtonClick}
|
||||
isLoading={isPaymentFormLoading}
|
||||
noForcedUpperCase
|
||||
>
|
||||
{lang('GiftSend', {
|
||||
{activeGiftAuction ? (
|
||||
<div>
|
||||
<div>
|
||||
{lang('GiftAuctionPlaceBid')}
|
||||
</div>
|
||||
{auctionTimeLeft !== undefined && auctionTimeLeft > 0 && (
|
||||
<div className={styles.buttonSubtitle}>
|
||||
{lang('GiftAuctionTimeLeft', {
|
||||
time: shouldUseTextTimer
|
||||
? <TextTimer endsAt={auctionEndDate!} />
|
||||
: formatCountdown(lang, auctionTimeLeft),
|
||||
}, { withNodes: true })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : lang('GiftSend', {
|
||||
amount,
|
||||
}, {
|
||||
withNodes: true,
|
||||
@ -434,6 +496,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
paidMessagesStars,
|
||||
areUniqueStarGiftsDisallowed,
|
||||
shouldDisallowLimitedStarGifts,
|
||||
activeGiftAuction: tabState.activeGiftAuction,
|
||||
};
|
||||
},
|
||||
)(GiftComposer));
|
||||
|
||||
@ -40,6 +40,14 @@
|
||||
&:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.noClickable {
|
||||
cursor: default;
|
||||
|
||||
&:hover::before {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.starGift {
|
||||
@ -80,6 +88,7 @@
|
||||
font-size: 0.75rem !important;
|
||||
}
|
||||
|
||||
.auction,
|
||||
.premiumRequired {
|
||||
outline: 0.125rem solid #D18D21;
|
||||
outline-offset: -0.125rem;
|
||||
|
||||
@ -28,8 +28,10 @@ export type OwnProps = {
|
||||
gift: ApiStarGift;
|
||||
isResale?: boolean;
|
||||
withTransferBadge?: boolean;
|
||||
hideBadge?: boolean;
|
||||
noClickable?: boolean;
|
||||
observeIntersection?: ObserveFn;
|
||||
onClick: (gift: ApiStarGift, target: 'original' | 'resell') => void;
|
||||
onClick?: (gift: ApiStarGift, target: 'original' | 'resell') => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -39,9 +41,11 @@ type StateProps = {
|
||||
const GIFT_STICKER_SIZE = 90;
|
||||
|
||||
function GiftItemStar({
|
||||
gift, isResale, isCurrentUserPremium, withTransferBadge, observeIntersection, onClick,
|
||||
gift, isResale, isCurrentUserPremium, withTransferBadge, hideBadge, noClickable, observeIntersection, onClick,
|
||||
}: OwnProps & StateProps) {
|
||||
const { openGiftInfoModal, openPremiumModal, showNotification, checkCanSendGift } = getActions();
|
||||
const {
|
||||
openGiftInfoModal, openPremiumModal, showNotification, checkCanSendGift, openGiftAuctionModal,
|
||||
} = getActions();
|
||||
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
const stickerRef = useRef<HTMLDivElement>();
|
||||
@ -77,11 +81,17 @@ function GiftItemStar({
|
||||
? lang.number(resellMinStars) + '+' : priceInfo?.amount || 0;
|
||||
const isLimited = !isGiftUnique && Boolean(regularGift?.isLimited);
|
||||
const isSoldOut = !isGiftUnique && Boolean(regularGift?.isSoldOut);
|
||||
const isAuction = !isGiftUnique && !isResale && Boolean(regularGift?.isAuction);
|
||||
const isPremiumRequired = Boolean(gift?.requirePremium);
|
||||
const isUserLimitReached = Boolean(regularGift?.limitedPerUser && !regularGift?.perUserRemains);
|
||||
const perUserTotal = regularGift?.perUserTotal;
|
||||
|
||||
const handleGiftClick = useLastCallback(() => {
|
||||
if (isAuction) {
|
||||
openGiftAuctionModal({ gift });
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSoldOut && !isResale) {
|
||||
openGiftInfoModal({ gift });
|
||||
return;
|
||||
@ -110,12 +120,12 @@ function GiftItemStar({
|
||||
if (isLocked) {
|
||||
checkCanSendGift({
|
||||
gift,
|
||||
onSuccess: () => onClick(gift, isResale ? 'resell' : 'original'),
|
||||
onSuccess: () => onClick?.(gift, isResale ? 'resell' : 'original'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
onClick(gift, isResale ? 'resell' : 'original');
|
||||
onClick?.(gift, isResale ? 'resell' : 'original');
|
||||
});
|
||||
|
||||
const radialPatternBackdrop = useMemo(() => {
|
||||
@ -162,14 +172,18 @@ function GiftItemStar({
|
||||
if (isSoldOut) {
|
||||
return <GiftRibbon color="red" text={lang('GiftSoldOut')} />;
|
||||
}
|
||||
if (isAuction) {
|
||||
return <GiftRibbon color="orange" text={lang('GiftRibbonAuction')} />;
|
||||
}
|
||||
if (isPremiumRequired) {
|
||||
return <GiftRibbon color="orange" text={lang('LimitPremium')} />;
|
||||
return <GiftRibbon color="orange" text={lang('GiftRibbonPremium')} />;
|
||||
}
|
||||
if (isLimited) {
|
||||
return <GiftRibbon color="blue" text={lang('GiftLimited')} />;
|
||||
}
|
||||
return undefined;
|
||||
}, [isGiftUnique, isResale, gift, isSoldOut, isLimited, lang, giftNumber, isPremiumRequired]);
|
||||
}, [isGiftUnique, isResale, gift, isSoldOut,
|
||||
isLimited, lang, giftNumber, isPremiumRequired, isAuction]);
|
||||
|
||||
useOnIntersect(ref, observeIntersection, (entry) => {
|
||||
const visible = entry.isIntersecting;
|
||||
@ -181,6 +195,10 @@ function GiftItemStar({
|
||||
return lang('GiftTransferTitle');
|
||||
}
|
||||
|
||||
if (isAuction) {
|
||||
return lang('GiftAuctionJoin');
|
||||
}
|
||||
|
||||
if (priceCurrency === TON_CURRENCY_CODE) {
|
||||
return formatTonAsIcon(lang, formattedPrice || 0, {
|
||||
shouldConvertFromNanos: true,
|
||||
@ -192,7 +210,7 @@ function GiftItemStar({
|
||||
asFont: true,
|
||||
className: styles.star,
|
||||
});
|
||||
}, [withTransferBadge, priceCurrency, formattedPrice, lang]);
|
||||
}, [withTransferBadge, priceCurrency, formattedPrice, isAuction, lang]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -203,12 +221,14 @@ function GiftItemStar({
|
||||
styles.starGift,
|
||||
'starGiftItem',
|
||||
isPremiumRequired && styles.premiumRequired,
|
||||
isAuction && styles.auction,
|
||||
noClickable && styles.noClickable,
|
||||
)}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={handleGiftClick}
|
||||
onMouseEnter={!IS_TOUCH_ENV ? markHover : undefined}
|
||||
onMouseLeave={!IS_TOUCH_ENV ? unmarkHover : undefined}
|
||||
tabIndex={noClickable ? undefined : 0}
|
||||
role={noClickable ? undefined : 'button'}
|
||||
onClick={noClickable ? undefined : handleGiftClick}
|
||||
onMouseEnter={!IS_TOUCH_ENV && !noClickable ? markHover : undefined}
|
||||
onMouseLeave={!IS_TOUCH_ENV && !noClickable ? unmarkHover : undefined}
|
||||
>
|
||||
{radialPatternBackdrop}
|
||||
|
||||
@ -230,20 +250,22 @@ function GiftItemStar({
|
||||
)}
|
||||
|
||||
</div>
|
||||
<Button
|
||||
className={buildClassName(
|
||||
styles.buy,
|
||||
withTransferBadge && styles.transferBadge,
|
||||
)}
|
||||
nonInteractive
|
||||
size="tiny"
|
||||
color={isGiftUnique ? 'bluredStarsBadge' : 'stars'}
|
||||
withSparkleEffect={isVisible && !withTransferBadge}
|
||||
pill
|
||||
fluid
|
||||
>
|
||||
{badgeContent}
|
||||
</Button>
|
||||
{!hideBadge && (
|
||||
<Button
|
||||
className={buildClassName(
|
||||
styles.buy,
|
||||
withTransferBadge && styles.transferBadge,
|
||||
)}
|
||||
nonInteractive
|
||||
size="tiny"
|
||||
color={isGiftUnique ? 'bluredStarsBadge' : 'stars'}
|
||||
withSparkleEffect={isVisible && !withTransferBadge}
|
||||
pill
|
||||
fluid
|
||||
>
|
||||
{badgeContent}
|
||||
</Button>
|
||||
)}
|
||||
{giftRibbon}
|
||||
{isLocked && <Icon name="lock-badge" className={styles.lockIcon} />}
|
||||
</div>
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
.root :global(.modal-dialog),
|
||||
.transition,
|
||||
.content {
|
||||
height: min(92vh, 49rem);
|
||||
height: min(98vh, 49rem);
|
||||
max-height: none !important;
|
||||
}
|
||||
|
||||
|
||||
@ -41,7 +41,7 @@ import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import Modal from '../../ui/Modal';
|
||||
import Transition from '../../ui/Transition';
|
||||
import BalanceBlock from '../stars/BalanceBlock';
|
||||
import GiftSendingOptions from './GiftComposer';
|
||||
import GiftComposer from './GiftComposer';
|
||||
import GiftItemPremium from './GiftItemPremium';
|
||||
import GiftItemStar from './GiftItemStar';
|
||||
import GiftModalResaleScreen from './GiftModalResaleScreen';
|
||||
@ -104,6 +104,8 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
||||
closeResaleGiftsMarket,
|
||||
loadMyUniqueGifts,
|
||||
openGiftTransferConfirmModal,
|
||||
setGiftModalSelectedGift,
|
||||
clearActiveGiftAuction,
|
||||
} = getActions();
|
||||
const dialogRef = useRef<HTMLDivElement>();
|
||||
const transitionRef = useRef<HTMLDivElement>();
|
||||
@ -118,7 +120,7 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
||||
const user = peer && isApiPeerUser(peer) ? peer : undefined;
|
||||
const chat = peer && isApiPeerChat(peer) ? peer : undefined;
|
||||
|
||||
const [selectedGift, setSelectedGift] = useState<GiftOption | undefined>();
|
||||
const selectedGift = renderingModal?.selectedGift;
|
||||
const [shouldShowMainScreenHeader, setShouldShowMainScreenHeader] = useState(false);
|
||||
const [isMainScreenHeaderForStarGifts, setIsMainScreenHeaderForStarGifts] = useState(false);
|
||||
const [isGiftScreenHeaderForStarGifts, setIsGiftScreenHeaderForStarGifts] = useState(false);
|
||||
@ -192,10 +194,14 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setShouldShowMainScreenHeader(false);
|
||||
setSelectedGift(undefined);
|
||||
setGiftModalSelectedGift({ gift: undefined });
|
||||
setSelectedCategory('all');
|
||||
}
|
||||
}, [isOpen, tabId, closeResaleGiftsMarket]);
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsGiftScreenHeaderForStarGifts(Boolean(selectedGift && 'id' in selectedGift));
|
||||
}, [selectedGift]);
|
||||
|
||||
const handleScroll = useLastCallback((e: React.UIEvent<HTMLDivElement>) => {
|
||||
if (isGiftScreen) return;
|
||||
@ -303,8 +309,7 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
||||
openGiftInMarket({ gift, tabId });
|
||||
return;
|
||||
}
|
||||
setSelectedGift(gift);
|
||||
setIsGiftScreenHeaderForStarGifts('id' in gift);
|
||||
setGiftModalSelectedGift({ gift });
|
||||
});
|
||||
|
||||
const handleMyGiftClick = useLastCallback((gift: ApiStarGift) => {
|
||||
@ -423,7 +428,7 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
const handleCloseModal = useLastCallback(() => {
|
||||
setSelectedGift(undefined);
|
||||
setGiftModalSelectedGift({ gift: undefined });
|
||||
resetResaleGifts();
|
||||
closeGiftModal();
|
||||
});
|
||||
@ -434,7 +439,8 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
||||
return;
|
||||
}
|
||||
if (isGiftScreen) {
|
||||
setSelectedGift(undefined);
|
||||
setGiftModalSelectedGift({ gift: undefined });
|
||||
clearActiveGiftAuction();
|
||||
return;
|
||||
}
|
||||
handleCloseModal();
|
||||
@ -589,7 +595,7 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
)}
|
||||
{isGiftScreen && renderingModal?.forPeerId && (
|
||||
<GiftSendingOptions
|
||||
<GiftComposer
|
||||
gift={selectedGift}
|
||||
giftByStars={giftsByStars.get(selectedGift)}
|
||||
peerId={renderingModal.forPeerId}
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import type { OwnProps } from './GiftAuctionAcquiredModal';
|
||||
|
||||
import { Bundles } from '../../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../../hooks/useModuleLoader';
|
||||
|
||||
const GiftAuctionAcquiredModalAsync = (props: OwnProps) => {
|
||||
const { modal } = props;
|
||||
const GiftAuctionAcquiredModal = useModuleLoader(Bundles.Stars, 'GiftAuctionAcquiredModal', !modal);
|
||||
|
||||
return GiftAuctionAcquiredModal ? <GiftAuctionAcquiredModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default GiftAuctionAcquiredModalAsync;
|
||||
@ -0,0 +1,50 @@
|
||||
.modal {
|
||||
max-width: 26rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 0 !important;
|
||||
padding-inline: 1.25rem !important;
|
||||
}
|
||||
|
||||
.giftsListContainer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.giftsList {
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
|
||||
max-height: 29.5rem;
|
||||
}
|
||||
|
||||
.giftHeader {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
|
||||
font-size: 0.9375rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.bidValue {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
margin-inline-start: 0.5rem;
|
||||
}
|
||||
|
||||
.starIcon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.okButton {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
133
src/components/modals/gift/auction/GiftAuctionAcquiredModal.tsx
Normal file
133
src/components/modals/gift/auction/GiftAuctionAcquiredModal.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
import { memo, useMemo, useRef } from '../../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../../global';
|
||||
|
||||
import type { ApiStarGiftAuctionAcquiredGift, ApiSticker } from '../../../../api/types';
|
||||
import type { TabState } from '../../../../global/types';
|
||||
|
||||
import { selectTabState } from '../../../../global/selectors';
|
||||
import { formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
||||
import { formatStarsAsIcon } from '../../../../util/localization/format';
|
||||
|
||||
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
import useScrollNotch from '../../../../hooks/useScrollNotch';
|
||||
|
||||
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
|
||||
import BadgeButton from '../../../common/BadgeButton';
|
||||
import Button from '../../../ui/Button';
|
||||
import Modal from '../../../ui/Modal';
|
||||
import TableInfo, { type TableData } from '../../common/TableInfo';
|
||||
|
||||
import styles from './GiftAuctionAcquiredModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['giftAuctionAcquiredModal'];
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
acquiredGifts?: ApiStarGiftAuctionAcquiredGift[];
|
||||
giftTitle?: string;
|
||||
giftSticker?: ApiSticker;
|
||||
};
|
||||
|
||||
const GiftAuctionAcquiredModal = ({ modal, acquiredGifts, giftTitle, giftSticker }: OwnProps & StateProps) => {
|
||||
const { closeGiftAuctionAcquiredModal } = getActions();
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>();
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const isOpen = Boolean(modal?.giftId);
|
||||
const renderingGifts = useCurrentOrPrev(acquiredGifts);
|
||||
const renderingGiftTitle = useCurrentOrPrev(giftTitle);
|
||||
const renderingGiftSticker = useCurrentOrPrev(giftSticker);
|
||||
|
||||
const handleClose = useLastCallback(() => {
|
||||
closeGiftAuctionAcquiredModal();
|
||||
});
|
||||
|
||||
const giftItems = useMemo(() => {
|
||||
if (!renderingGifts?.length) return undefined;
|
||||
|
||||
return renderingGifts.map((gift) => {
|
||||
const header = lang('GiftAuctionBoughtGiftHeader', {
|
||||
gift: renderingGiftTitle || lang('StarGift'),
|
||||
giftNumber: gift.giftNumber ? lang.number(gift.giftNumber) : '',
|
||||
round: lang.number(gift.round),
|
||||
});
|
||||
|
||||
const tableData: TableData = [
|
||||
[undefined, (
|
||||
<span className={styles.giftHeader}>
|
||||
{renderingGiftSticker && (
|
||||
<AnimatedIconFromSticker
|
||||
className={styles.giftSticker}
|
||||
sticker={renderingGiftSticker}
|
||||
size={20}
|
||||
play={false}
|
||||
/>
|
||||
)}
|
||||
<span>{header}</span>
|
||||
</span>
|
||||
)],
|
||||
[lang('GiftAuctionRecipient'), { chatId: gift.peerId }],
|
||||
[lang('GiftAuctionDate'), formatDateTimeToString(gift.date * 1000, lang.code, true)],
|
||||
[lang('GiftAuctionAcceptedBid'), (
|
||||
<span className={styles.bidValue}>
|
||||
{formatStarsAsIcon(lang, gift.bidAmount, { className: styles.starIcon })}
|
||||
<BadgeButton className={styles.badge}>
|
||||
{lang('GiftAuctionTopPosition', { position: gift.position })}
|
||||
</BadgeButton>
|
||||
</span>
|
||||
)],
|
||||
];
|
||||
|
||||
return { tableData, key: `${gift.round}-${gift.giftNumber}` };
|
||||
});
|
||||
}, [renderingGifts, renderingGiftTitle, renderingGiftSticker, lang]);
|
||||
|
||||
const giftsCount = renderingGifts?.length || 0;
|
||||
|
||||
useScrollNotch({
|
||||
containerRef,
|
||||
selector: `.${styles.giftsList}`,
|
||||
isBottomNotch: true,
|
||||
}, [giftItems]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
hasCloseButton
|
||||
title={lang('GiftAuctionBoughtGiftsTitle', { count: giftsCount }, { pluralValue: giftsCount })}
|
||||
className={styles.modal}
|
||||
contentClassName={styles.content}
|
||||
onClose={handleClose}
|
||||
isCondensedHeader
|
||||
isSlim
|
||||
>
|
||||
<div className={styles.giftsListContainer} ref={containerRef}>
|
||||
<div className={styles.giftsList}>
|
||||
{giftItems?.map((item) => (
|
||||
<TableInfo key={item.key} tableData={item.tableData} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Button className={styles.okButton} onClick={handleClose}>
|
||||
{lang('OK')}
|
||||
</Button>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): Complete<StateProps> => {
|
||||
const { giftAuctionAcquiredModal } = selectTabState(global);
|
||||
|
||||
return {
|
||||
acquiredGifts: giftAuctionAcquiredModal?.acquiredGifts,
|
||||
giftTitle: giftAuctionAcquiredModal?.giftTitle,
|
||||
giftSticker: giftAuctionAcquiredModal?.giftSticker,
|
||||
};
|
||||
},
|
||||
)(GiftAuctionAcquiredModal));
|
||||
@ -0,0 +1,14 @@
|
||||
import type { OwnProps } from './GiftAuctionBidModal';
|
||||
|
||||
import { Bundles } from '../../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../../hooks/useModuleLoader';
|
||||
|
||||
const GiftAuctionBidModalAsync = (props: OwnProps) => {
|
||||
const { modal } = props;
|
||||
const GiftAuctionBidModal = useModuleLoader(Bundles.Stars, 'GiftAuctionBidModal', !modal);
|
||||
|
||||
return GiftAuctionBidModal ? <GiftAuctionBidModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default GiftAuctionBidModalAsync;
|
||||
@ -0,0 +1,232 @@
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
max-height: min(92vh, 38rem) !important;
|
||||
}
|
||||
|
||||
.headerControlPanel {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 0.75rem;
|
||||
right: 1.25rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.slider {
|
||||
flex-shrink: 0;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
font-size: 1.5rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.infoCards {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.infoCard {
|
||||
flex: 1;
|
||||
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.75rem;
|
||||
|
||||
text-align: center;
|
||||
|
||||
background: var(--color-background-secondary);
|
||||
}
|
||||
|
||||
.infoCardValue {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
font-size: 1.125rem;
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.infoCardLabel {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.winningStatus,
|
||||
.bidderInfoSlide {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.winningText {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-green);
|
||||
}
|
||||
|
||||
.winningBadge {
|
||||
padding: 0.0625rem 0.5rem;
|
||||
border-radius: 0.875rem;
|
||||
|
||||
font-size: 0.75rem;
|
||||
line-height: 0.875rem;
|
||||
color: var(--color-text-green);
|
||||
|
||||
background-color: rgba(var(--color-text-green-rgb), 0.1);
|
||||
}
|
||||
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.sectionTitleTransition {
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.bidderRow {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
||||
.bidderPosition {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 1.5rem;
|
||||
min-width: 1.5rem;
|
||||
|
||||
font-size: 0.875rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.bidderInfo {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
|
||||
height: 2.125rem;
|
||||
}
|
||||
|
||||
.bidderName {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
|
||||
font-size: 0.9375rem;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.topBidderRow {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
align-items: center;
|
||||
|
||||
padding: 0.25rem 0;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 4.4375rem;
|
||||
|
||||
height: 1px;
|
||||
|
||||
opacity: 0.75;
|
||||
background-color: var(--color-borders);
|
||||
}
|
||||
|
||||
&:last-child::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.topBidderPosition {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 1.5rem;
|
||||
min-width: 1.5rem;
|
||||
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.bidderAmount {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
align-items: center;
|
||||
|
||||
font-size: 0.9375rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.buttonStar {
|
||||
margin-inline-start: 0 !important;
|
||||
}
|
||||
|
||||
.giftSticker {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.customBidInput {
|
||||
position: relative;
|
||||
|
||||
:global(.input-group) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:global(.form-control) {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.customBidInputIcon {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 50%;
|
||||
left: 0.75rem;
|
||||
transform: translateY(-50%);
|
||||
|
||||
font-size: 1rem;
|
||||
}
|
||||
422
src/components/modals/gift/auction/GiftAuctionBidModal.tsx
Normal file
422
src/components/modals/gift/auction/GiftAuctionBidModal.tsx
Normal file
@ -0,0 +1,422 @@
|
||||
import { memo, useEffect, useMemo, useState } from '../../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
||||
|
||||
import type {
|
||||
ApiPeer,
|
||||
ApiStarGiftAuctionState,
|
||||
ApiStarsAmount,
|
||||
} from '../../../../api/types';
|
||||
import type { TabState } from '../../../../global/types';
|
||||
|
||||
import { selectPeer, selectTabState } from '../../../../global/selectors';
|
||||
import { formatStarsAsIcon } from '../../../../util/localization/format';
|
||||
import renderText from '../../../common/helpers/renderText';
|
||||
|
||||
import { useTransitionActiveKey } from '../../../../hooks/animations/useTransitionActiveKey';
|
||||
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||
import useFlag from '../../../../hooks/useFlag';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
|
||||
import AnimatedCounter from '../../../common/AnimatedCounter';
|
||||
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
|
||||
import Avatar from '../../../common/Avatar';
|
||||
import FullNameTitle from '../../../common/FullNameTitle';
|
||||
import StarIcon from '../../../common/icons/StarIcon';
|
||||
import Button from '../../../ui/Button';
|
||||
import ConfirmDialog from '../../../ui/ConfirmDialog';
|
||||
import InputText from '../../../ui/InputText';
|
||||
import Modal from '../../../ui/Modal';
|
||||
import TextTimer from '../../../ui/TextTimer';
|
||||
import Transition from '../../../ui/Transition';
|
||||
import StarSlider from '../../paidReaction/StarSlider';
|
||||
import BalanceBlock from '../../stars/BalanceBlock';
|
||||
|
||||
import styles from './GiftAuctionBidModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['giftAuctionBidModal'];
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
auctionState?: ApiStarGiftAuctionState;
|
||||
starBalance?: ApiStarsAmount;
|
||||
currentUserPeer?: ApiPeer;
|
||||
topBidderIds?: string[];
|
||||
};
|
||||
|
||||
const DEFAULT_BID_AMOUNT = 50;
|
||||
const MAX_BID_AMOUNT_STEP = 50000;
|
||||
const MAX_CUSTOM_BID_AMOUNT = 1000000000;
|
||||
const BID_ROUNDING_STEP = 10000;
|
||||
const MIN_SLIDER_PROGRESS = 0.25;
|
||||
const GIFT_STICKER_SIZE = 24;
|
||||
const DEFAULT_TOP_BIDDERS_COUNT = 3;
|
||||
|
||||
const GiftAuctionBidModal = ({
|
||||
modal,
|
||||
auctionState,
|
||||
starBalance,
|
||||
currentUserPeer,
|
||||
topBidderIds,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { closeGiftAuctionBidModal, sendStarGiftAuctionBid, loadActiveGiftAuction } = getActions();
|
||||
|
||||
const isOpen = Boolean(modal?.isOpen);
|
||||
|
||||
const renderingAuctionState = useCurrentOrPrev(auctionState);
|
||||
const renderingTopBidderIds = useCurrentOrPrev(topBidderIds);
|
||||
|
||||
const renderingTopBidderPeers = useMemo(() => {
|
||||
if (!renderingTopBidderIds) return undefined;
|
||||
const global = getGlobal();
|
||||
return renderingTopBidderIds
|
||||
.map((id) => selectPeer(global, id))
|
||||
.filter(Boolean);
|
||||
}, [renderingTopBidderIds]);
|
||||
|
||||
const [topBidder1, topBidder2, topBidder3] = renderingTopBidderPeers || [];
|
||||
|
||||
const topBidder1Key = useTransitionActiveKey([topBidder1?.id || '0']);
|
||||
const topBidder2Key = useTransitionActiveKey([topBidder2?.id || '0']);
|
||||
const topBidder3Key = useTransitionActiveKey([topBidder3?.id || '0']);
|
||||
|
||||
const giftsPerRound = renderingAuctionState?.gift.giftsPerRound || 0;
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const activeState = renderingAuctionState?.state.type === 'active'
|
||||
? renderingAuctionState.state
|
||||
: undefined;
|
||||
const userState = renderingAuctionState?.userState;
|
||||
|
||||
const [selectedBidAmount, setSelectedBidAmount] = useState(DEFAULT_BID_AMOUNT);
|
||||
const [isCustomBidModalOpen, openCustomBidModal, closeCustomBidModal] = useFlag();
|
||||
const [customBidValue, setCustomBidValue] = useState('');
|
||||
|
||||
const baseMinBid = activeState?.minBidAmount || DEFAULT_BID_AMOUNT;
|
||||
|
||||
const currentMinBid = userState?.minBidAmount || baseMinBid;
|
||||
|
||||
const sliderMaxValue = Math.ceil(currentMinBid / BID_ROUNDING_STEP) * BID_ROUNDING_STEP + MAX_BID_AMOUNT_STEP;
|
||||
|
||||
const currentProgress = (currentMinBid - baseMinBid) / (sliderMaxValue - baseMinBid);
|
||||
const adjustedMinBid = Math.floor(
|
||||
(currentMinBid - MIN_SLIDER_PROGRESS * sliderMaxValue) / (1 - MIN_SLIDER_PROGRESS),
|
||||
);
|
||||
const giftMinBid = currentProgress > MIN_SLIDER_PROGRESS
|
||||
? Math.max(1, adjustedMinBid)
|
||||
: baseMinBid;
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedBidAmount(currentMinBid);
|
||||
}, [currentMinBid]);
|
||||
|
||||
const nextRoundAt = activeState?.nextRoundAt;
|
||||
|
||||
const bidDifference = userState?.bidAmount ? selectedBidAmount - userState.bidAmount : 0;
|
||||
const isAtMaxValue = selectedBidAmount >= sliderMaxValue;
|
||||
|
||||
const sliderSecondaryText = useMemo(() => {
|
||||
if (isAtMaxValue) return lang('GiftAuctionTapToBidMore');
|
||||
if (bidDifference <= 0) return undefined;
|
||||
return (
|
||||
<>
|
||||
+
|
||||
<AnimatedCounter text={lang.number(bidDifference)} />
|
||||
</>
|
||||
);
|
||||
}, [bidDifference, isAtMaxValue, lang]);
|
||||
|
||||
const handleAmountChange = useLastCallback((value: number) => {
|
||||
setSelectedBidAmount(value);
|
||||
});
|
||||
|
||||
const handleTimerEnd = useLastCallback(() => {
|
||||
if (!renderingAuctionState?.gift.id) return;
|
||||
loadActiveGiftAuction({ giftId: renderingAuctionState.gift.id });
|
||||
});
|
||||
|
||||
const handleBadgeClick = useLastCallback(() => {
|
||||
if (isAtMaxValue) {
|
||||
openCustomBidModal();
|
||||
}
|
||||
});
|
||||
|
||||
const handleCustomBidChange = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value.replace(/\D/g, '');
|
||||
const numValue = Number(value);
|
||||
if (numValue > MAX_CUSTOM_BID_AMOUNT) return;
|
||||
setCustomBidValue(value);
|
||||
});
|
||||
|
||||
const handleCustomBidSubmit = useLastCallback(() => {
|
||||
if (!renderingAuctionState?.gift.id || !modal) return;
|
||||
|
||||
const resultValue = Number(customBidValue);
|
||||
if (resultValue < currentMinBid) return;
|
||||
|
||||
setSelectedBidAmount(resultValue);
|
||||
closeCustomBidModal();
|
||||
|
||||
setCustomBidValue('');
|
||||
|
||||
const { peerId, message, shouldHideName } = modal;
|
||||
const isUpdateBid = Boolean(userState?.bidAmount);
|
||||
|
||||
sendStarGiftAuctionBid({
|
||||
giftId: renderingAuctionState.gift.id,
|
||||
bidAmount: resultValue,
|
||||
peerId,
|
||||
message: message ? { text: message } : undefined,
|
||||
shouldHideName,
|
||||
isUpdateBid,
|
||||
});
|
||||
});
|
||||
|
||||
const handleSubmit = useLastCallback(() => {
|
||||
if (!renderingAuctionState?.gift.id || !modal) return;
|
||||
const { peerId, message, shouldHideName } = modal;
|
||||
const isUpdateBid = Boolean(userState?.bidAmount);
|
||||
|
||||
sendStarGiftAuctionBid({
|
||||
giftId: renderingAuctionState.gift.id,
|
||||
bidAmount: selectedBidAmount,
|
||||
peerId,
|
||||
message: message ? { text: message } : undefined,
|
||||
shouldHideName,
|
||||
isUpdateBid,
|
||||
});
|
||||
});
|
||||
|
||||
const userPosition = useMemo(() => {
|
||||
if (!selectedBidAmount || !activeState?.bidLevels?.length) return undefined;
|
||||
|
||||
const { bidLevels } = activeState;
|
||||
const userBidDate = userState?.bidDate || Number.MAX_SAFE_INTEGER;
|
||||
|
||||
for (const level of bidLevels) {
|
||||
if (level.amount < selectedBidAmount
|
||||
|| (level.amount === selectedBidAmount && level.date >= userBidDate)) {
|
||||
return level.pos;
|
||||
}
|
||||
}
|
||||
|
||||
return bidLevels[bidLevels.length - 1].pos + 1;
|
||||
}, [selectedBidAmount, activeState, userState?.bidDate]);
|
||||
|
||||
function renderInfoCards() {
|
||||
return (
|
||||
<div className={styles.infoCards}>
|
||||
<div className={styles.infoCard}>
|
||||
<div className={styles.infoCardValue}>
|
||||
<StarIcon type="gold" size="adaptive" />
|
||||
{lang.number(currentMinBid)}
|
||||
</div>
|
||||
<div className={styles.infoCardLabel}>{lang('GiftAuctionMinimumBid')}</div>
|
||||
</div>
|
||||
<div className={styles.infoCard}>
|
||||
<div className={styles.infoCardValue}>
|
||||
<TextTimer endsAt={nextRoundAt || 0} shouldShowZeroOnEnd onEnd={handleTimerEnd} />
|
||||
</div>
|
||||
<div className={styles.infoCardLabel}>{lang('GiftAuctionUntilNextRound')}</div>
|
||||
</div>
|
||||
<div className={styles.infoCard}>
|
||||
<div className={styles.infoCardValue}>
|
||||
<AnimatedIconFromSticker
|
||||
noLoop={false}
|
||||
className={styles.giftSticker}
|
||||
sticker={renderingAuctionState?.gift.sticker}
|
||||
size={GIFT_STICKER_SIZE}
|
||||
/>
|
||||
{lang.number(activeState?.giftsLeft || 0)}
|
||||
</div>
|
||||
<div className={styles.infoCardLabel}>{lang('GiftAuctionLeft')}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isWinning = Boolean(userState?.bidAmount && userPosition && userPosition <= giftsPerRound);
|
||||
|
||||
function renderCurrentBidSectionTitle() {
|
||||
const giftTitle = renderingAuctionState?.gift.title || lang('StarGift');
|
||||
const nextGiftNum = userPosition && userPosition <= 100
|
||||
? (activeState?.lastGiftNum || 0) + userPosition
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Transition
|
||||
name="fade"
|
||||
activeKey={isWinning ? 0 : 1}
|
||||
className={styles.sectionTitleTransition}
|
||||
slideClassName={styles.bidderInfoSlide}
|
||||
>
|
||||
{isWinning ? (
|
||||
<div className={styles.winningStatus}>
|
||||
<span className={styles.winningText}>{lang('GiftAuctionYoureWinning')}</span>
|
||||
<span className={styles.winningBadge}>
|
||||
{lang('GiftUnique', { title: giftTitle, number: nextGiftNum ? lang.number(nextGiftNum) : undefined })}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.sectionTitle}>{lang('GiftAuctionYourBidWillBe')}</div>
|
||||
)}
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
||||
function renderUserBid() {
|
||||
return (
|
||||
<div className={styles.section}>
|
||||
{renderCurrentBidSectionTitle()}
|
||||
<div className={styles.bidderRow}>
|
||||
<div className={styles.bidderPosition}>
|
||||
{userPosition && userPosition > 100 ? `${userPosition}+` : (userPosition || 1)}
|
||||
</div>
|
||||
<div className={styles.bidderInfo}>
|
||||
{currentUserPeer && <Avatar peer={currentUserPeer} size="small" />}
|
||||
{currentUserPeer && <FullNameTitle peer={currentUserPeer} className={styles.bidderName} />}
|
||||
</div>
|
||||
<div className={styles.bidderAmount}>
|
||||
<StarIcon type="gold" size="adaptive" />
|
||||
{lang.number(selectedBidAmount)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderTopBidderRow(
|
||||
index: number,
|
||||
emoji: string,
|
||||
peer: ApiPeer | undefined,
|
||||
amount: number | undefined,
|
||||
activeKey: number,
|
||||
) {
|
||||
return (
|
||||
<div className={styles.topBidderRow} key={index}>
|
||||
<div className={styles.topBidderPosition}>
|
||||
{renderText(emoji, ['emoji'])}
|
||||
</div>
|
||||
<Transition
|
||||
name="fade"
|
||||
activeKey={activeKey}
|
||||
className={styles.bidderInfo}
|
||||
slideClassName={styles.bidderInfoSlide}
|
||||
>
|
||||
{peer && (
|
||||
<>
|
||||
<Avatar peer={peer} size="small" />
|
||||
<FullNameTitle peer={peer} className={styles.bidderName} />
|
||||
</>
|
||||
)}
|
||||
</Transition>
|
||||
{amount !== undefined && (
|
||||
<div className={styles.bidderAmount}>
|
||||
<StarIcon type="gold" size="adaptive" />
|
||||
{lang.number(amount)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderTopWinners() {
|
||||
const topCount = DEFAULT_TOP_BIDDERS_COUNT;
|
||||
const bidLevels = activeState?.bidLevels;
|
||||
|
||||
return (
|
||||
<div className={styles.section}>
|
||||
<div className={styles.sectionTitle}>
|
||||
{lang('GiftAuctionTopWinners', { count: topCount }, { pluralValue: topCount })}
|
||||
</div>
|
||||
{renderTopBidderRow(0, '🥇', topBidder1, bidLevels?.[0]?.amount, topBidder1Key)}
|
||||
{renderTopBidderRow(1, '🥈', topBidder2, bidLevels?.[1]?.amount, topBidder2Key)}
|
||||
{renderTopBidderRow(2, '🥉', topBidder3, bidLevels?.[2]?.amount, topBidder3Key)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
hasAbsoluteCloseButton
|
||||
isSlim
|
||||
contentClassName={styles.content}
|
||||
onClose={closeGiftAuctionBidModal}
|
||||
isLowStackPriority
|
||||
>
|
||||
<div className={styles.headerControlPanel}>
|
||||
<BalanceBlock balance={starBalance} className={styles.modalBalance} withAddButton />
|
||||
</div>
|
||||
|
||||
<StarSlider
|
||||
className={styles.slider}
|
||||
defaultValue={currentMinBid}
|
||||
minValue={giftMinBid}
|
||||
minAllowedValue={currentMinBid}
|
||||
maxValue={sliderMaxValue}
|
||||
floatingBadgeDescription={sliderSecondaryText}
|
||||
onChange={handleAmountChange}
|
||||
onBadgeClick={handleBadgeClick}
|
||||
shouldUseDynamicColor
|
||||
shouldAllowCustomValue
|
||||
/>
|
||||
|
||||
<h3 className={styles.title}>{lang('GiftAuctionPlaceBid')}</h3>
|
||||
{renderInfoCards()}
|
||||
|
||||
{renderUserBid()}
|
||||
{renderTopWinners()}
|
||||
|
||||
<Button noForcedUpperCase onClick={handleSubmit}>
|
||||
{lang(userState?.bidAmount ? 'GiftAuctionAddToBid' : 'GiftAuctionPlaceBidButton', {
|
||||
amount: formatStarsAsIcon(lang,
|
||||
userState?.bidAmount ? selectedBidAmount - userState.bidAmount : selectedBidAmount,
|
||||
{ asFont: true, className: styles.buttonStar }),
|
||||
}, { withNodes: true })}
|
||||
</Button>
|
||||
<ConfirmDialog
|
||||
isOpen={isCustomBidModalOpen}
|
||||
title={lang('GiftAuctionCustomBidTitle')}
|
||||
confirmLabel={lang('GiftAuctionCustomBidButton')}
|
||||
isConfirmDisabled={!customBidValue || Number(customBidValue) < currentMinBid}
|
||||
confirmHandler={handleCustomBidSubmit}
|
||||
onClose={closeCustomBidModal}
|
||||
>
|
||||
<p>{lang('GiftAuctionCustomBidDescription', { count: renderingAuctionState?.gift.giftsPerRound })}</p>
|
||||
<div className={styles.customBidInput}>
|
||||
<StarIcon type="gold" size="adaptive" className={styles.customBidInputIcon} />
|
||||
<InputText
|
||||
value={customBidValue}
|
||||
onChange={handleCustomBidChange}
|
||||
placeholder={lang('GiftAuctionCustomBidPlaceholder')}
|
||||
inputMode="numeric"
|
||||
teactExperimentControlled
|
||||
/>
|
||||
</div>
|
||||
</ConfirmDialog>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): Complete<StateProps> => {
|
||||
const { activeGiftAuction } = selectTabState(global);
|
||||
const { stars, currentUserId } = global;
|
||||
|
||||
const currentUserPeer = currentUserId ? selectPeer(global, currentUserId) : undefined;
|
||||
|
||||
const topBidderIds = activeGiftAuction?.state.type === 'active'
|
||||
? activeGiftAuction.state.topBidders
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
auctionState: activeGiftAuction,
|
||||
starBalance: stars?.balance,
|
||||
currentUserPeer,
|
||||
topBidderIds,
|
||||
};
|
||||
},
|
||||
)(GiftAuctionBidModal));
|
||||
@ -0,0 +1,18 @@
|
||||
import type { OwnProps } from './GiftAuctionChangeRecipientModal';
|
||||
|
||||
import { Bundles } from '../../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../../hooks/useModuleLoader';
|
||||
|
||||
const GiftAuctionChangeRecipientModalAsync = (props: OwnProps) => {
|
||||
const { modal } = props;
|
||||
const GiftAuctionChangeRecipientModal = useModuleLoader(
|
||||
Bundles.Stars,
|
||||
'GiftAuctionChangeRecipientModal',
|
||||
!modal,
|
||||
);
|
||||
|
||||
return GiftAuctionChangeRecipientModal ? <GiftAuctionChangeRecipientModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default GiftAuctionChangeRecipientModalAsync;
|
||||
@ -0,0 +1,14 @@
|
||||
.preview {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 2rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
import { memo } from '../../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../../global';
|
||||
|
||||
import type { ApiPeer } from '../../../../api/types';
|
||||
import type { TabState } from '../../../../global/types';
|
||||
|
||||
import { getPeerTitle } from '../../../../global/helpers/peers';
|
||||
import { selectPeer } from '../../../../global/selectors';
|
||||
import { REM } from '../../../common/helpers/mediaDimensions';
|
||||
|
||||
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
|
||||
import Avatar from '../../../common/Avatar';
|
||||
import Icon from '../../../common/icons/Icon';
|
||||
import ConfirmDialog from '../../../ui/ConfirmDialog';
|
||||
|
||||
import styles from './GiftAuctionChangeRecipientModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['giftAuctionChangeRecipientModal'];
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
oldPeer?: ApiPeer;
|
||||
newPeer?: ApiPeer;
|
||||
};
|
||||
|
||||
const AVATAR_SIZE = 4 * REM;
|
||||
|
||||
const GiftAuctionChangeRecipientModal = ({ modal, oldPeer, newPeer }: OwnProps & StateProps) => {
|
||||
const { closeGiftAuctionChangeRecipientModal, openGiftAuctionBidModal } = getActions();
|
||||
const lang = useLang();
|
||||
|
||||
const isOpen = Boolean(modal?.isOpen);
|
||||
const renderingOldPeer = useCurrentOrPrev(oldPeer);
|
||||
const renderingNewPeer = useCurrentOrPrev(newPeer);
|
||||
const renderingModal = useCurrentOrPrev(modal);
|
||||
|
||||
const handleConfirm = useLastCallback(() => {
|
||||
if (!renderingModal) return;
|
||||
|
||||
closeGiftAuctionChangeRecipientModal();
|
||||
openGiftAuctionBidModal({
|
||||
peerId: renderingModal.newPeerId,
|
||||
message: renderingModal.message,
|
||||
shouldHideName: renderingModal.shouldHideName,
|
||||
});
|
||||
});
|
||||
|
||||
if (!renderingOldPeer || !renderingNewPeer) return undefined;
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
isOpen={isOpen}
|
||||
title={lang('GiftAuctionChangeRecipientTitle')}
|
||||
onClose={closeGiftAuctionChangeRecipientModal}
|
||||
confirmLabel={lang('Continue')}
|
||||
confirmHandler={handleConfirm}
|
||||
>
|
||||
<div className={styles.preview}>
|
||||
<Avatar peer={renderingOldPeer} size={AVATAR_SIZE} />
|
||||
<Icon name="next" className={styles.arrow} />
|
||||
<Avatar peer={renderingNewPeer} size={AVATAR_SIZE} />
|
||||
</div>
|
||||
<p>
|
||||
{lang('GiftAuctionChangeRecipientDescription', {
|
||||
oldPeer: getPeerTitle(lang, renderingOldPeer),
|
||||
newPeer: getPeerTitle(lang, renderingNewPeer),
|
||||
}, {
|
||||
withNodes: true,
|
||||
withMarkdown: true,
|
||||
})}
|
||||
</p>
|
||||
</ConfirmDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(
|
||||
withGlobal<OwnProps>((global, { modal }): Complete<StateProps> => {
|
||||
const oldPeer = modal?.oldPeerId ? selectPeer(global, modal.oldPeerId) : undefined;
|
||||
const newPeer = modal?.newPeerId ? selectPeer(global, modal.newPeerId) : undefined;
|
||||
|
||||
return {
|
||||
oldPeer,
|
||||
newPeer,
|
||||
};
|
||||
})(GiftAuctionChangeRecipientModal),
|
||||
);
|
||||
@ -0,0 +1,14 @@
|
||||
import type { OwnProps } from './GiftAuctionInfoModal';
|
||||
|
||||
import { Bundles } from '../../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../../hooks/useModuleLoader';
|
||||
|
||||
const GiftAuctionInfoModalAsync = (props: OwnProps) => {
|
||||
const { modal } = props;
|
||||
const GiftAuctionInfoModal = useModuleLoader(Bundles.Stars, 'GiftAuctionInfoModal', !modal?.isOpen);
|
||||
|
||||
return GiftAuctionInfoModal ? <GiftAuctionInfoModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default GiftAuctionInfoModalAsync;
|
||||
@ -0,0 +1,53 @@
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
margin-top: 0.125rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.iconWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border-radius: 50%;
|
||||
|
||||
font-size: 1rem;
|
||||
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 2.5rem;
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.title {
|
||||
padding-top: 0.25rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
padding-top: 0.25rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: stretch;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.understoodIcon {
|
||||
font-size: 1.1875rem;
|
||||
}
|
||||
101
src/components/modals/gift/auction/GiftAuctionInfoModal.tsx
Normal file
101
src/components/modals/gift/auction/GiftAuctionInfoModal.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import { memo, useMemo } from '../../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../../global';
|
||||
|
||||
import type { ApiStarGiftAuctionState } from '../../../../api/types';
|
||||
import type { TabState } from '../../../../global/types';
|
||||
|
||||
import { selectTabState } from '../../../../global/selectors';
|
||||
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
|
||||
import Icon from '../../../common/icons/Icon';
|
||||
import Button from '../../../ui/Button';
|
||||
import TableAboutModal, { type TableAboutData } from '../../common/TableAboutModal';
|
||||
|
||||
import styles from './GiftAuctionInfoModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['giftAuctionInfoModal'];
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
activeGiftAuction?: ApiStarGiftAuctionState;
|
||||
};
|
||||
|
||||
const GiftAuctionInfoModal = ({
|
||||
modal,
|
||||
activeGiftAuction,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { closeGiftAuctionInfoModal } = getActions();
|
||||
const lang = useLang();
|
||||
|
||||
const isOpen = Boolean(modal?.isOpen && activeGiftAuction);
|
||||
|
||||
const handleClose = useLastCallback(() => {
|
||||
closeGiftAuctionInfoModal();
|
||||
});
|
||||
|
||||
const header = useMemo(() => {
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<Icon name="auction-filled" className={styles.icon} />
|
||||
</div>
|
||||
<div className={styles.title}>
|
||||
{lang('GiftAuctionInfoTitle')}
|
||||
</div>
|
||||
<div className={styles.subtitle}>
|
||||
{lang('GiftAuctionInfoSubtitle')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, [lang]);
|
||||
|
||||
const footer = useMemo(() => {
|
||||
if (!isOpen) return undefined;
|
||||
return (
|
||||
<div className={styles.footer}>
|
||||
<Button
|
||||
iconName="understood"
|
||||
iconClassName={styles.understoodIcon}
|
||||
onClick={handleClose}
|
||||
>
|
||||
{lang('ButtonUnderstood')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}, [lang, isOpen, handleClose]);
|
||||
|
||||
const listItemData = useMemo(() => {
|
||||
const count = activeGiftAuction?.gift.giftsPerRound || 0;
|
||||
return [
|
||||
['auction-drop', lang('GiftAuctionInfoTopBiddersTitle', { count }, { pluralValue: count }),
|
||||
lang('GiftAuctionInfoTopBiddersSubtitle', { count }, { pluralValue: count })],
|
||||
['auction-next-round', lang('GiftAuctionInfoBidCarryoverTitle'),
|
||||
lang('GiftAuctionInfoBidCarryoverSubtitle', { count })],
|
||||
['stars-refund', lang('GiftAuctionInfoMissedBiddersTitle'),
|
||||
lang('GiftAuctionInfoMissedBiddersSubtitle')],
|
||||
] satisfies TableAboutData;
|
||||
}, [lang, activeGiftAuction]);
|
||||
|
||||
return (
|
||||
<TableAboutModal
|
||||
isOpen={isOpen}
|
||||
header={header}
|
||||
listItemData={listItemData}
|
||||
footer={footer}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): Complete<StateProps> => {
|
||||
const { activeGiftAuction } = selectTabState(global);
|
||||
|
||||
return {
|
||||
activeGiftAuction,
|
||||
};
|
||||
},
|
||||
)(GiftAuctionInfoModal));
|
||||
@ -0,0 +1,14 @@
|
||||
import type { OwnProps } from './GiftAuctionModal';
|
||||
|
||||
import { Bundles } from '../../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../../hooks/useModuleLoader';
|
||||
|
||||
const GiftAuctionModalAsync = (props: OwnProps) => {
|
||||
const { modal } = props;
|
||||
const GiftAuctionModal = useModuleLoader(Bundles.Stars, 'GiftAuctionModal', !modal);
|
||||
|
||||
return GiftAuctionModal ? <GiftAuctionModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default GiftAuctionModalAsync;
|
||||
@ -0,0 +1,82 @@
|
||||
.modal :global(.modal-dialog) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modalContent {
|
||||
position: relative;
|
||||
max-height: min(97vh, 48rem) !important;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
margin-top: 0.25rem;
|
||||
|
||||
font-size: 1.5rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.finishedBadge {
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 1rem;
|
||||
|
||||
font-size: 0.8125rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
background-color: var(--color-background-secondary);
|
||||
}
|
||||
|
||||
.giftName {
|
||||
font-weight: var(--font-weight-bold);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.itemsBoughtLink {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.itemsBoughtSticker {
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.footerButton {
|
||||
width: 100%;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.buttonSubtitle {
|
||||
display: block;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: var(--font-weight-normal);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.starIcon {
|
||||
line-height: 1rem !important;
|
||||
}
|
||||
220
src/components/modals/gift/auction/GiftAuctionModal.tsx
Normal file
220
src/components/modals/gift/auction/GiftAuctionModal.tsx
Normal file
@ -0,0 +1,220 @@
|
||||
import { memo, useMemo } from '../../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../../global';
|
||||
|
||||
import type { ApiStarGiftAuctionState } from '../../../../api/types';
|
||||
import type { TabState } from '../../../../global/types';
|
||||
|
||||
import { selectTabState } from '../../../../global/selectors';
|
||||
import { formatCountdown, formatDateTimeToString } from '../../../../util/dates/dateFormat';
|
||||
import { HOUR } from '../../../../util/dates/units';
|
||||
import { formatStarsAsIcon } from '../../../../util/localization/format';
|
||||
import { getServerTime } from '../../../../util/serverTime';
|
||||
import { getStickerFromGift } from '../../../common/helpers/gifts';
|
||||
|
||||
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
|
||||
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
|
||||
import Button from '../../../ui/Button';
|
||||
import Link from '../../../ui/Link';
|
||||
import TextTimer from '../../../ui/TextTimer';
|
||||
import TableInfoModal, { type TableData } from '../../common/TableInfoModal';
|
||||
import GiftItemStar from '../GiftItemStar';
|
||||
|
||||
import styles from './GiftAuctionModal.module.scss';
|
||||
|
||||
const TEXT_TIMER_THRESHOLD = 48 * HOUR;
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['giftAuctionModal'];
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
auctionState?: ApiStarGiftAuctionState;
|
||||
};
|
||||
|
||||
const GiftAuctionModal = ({ modal, auctionState }: OwnProps & StateProps) => {
|
||||
const {
|
||||
closeGiftAuctionModal,
|
||||
setGiftModalSelectedGift,
|
||||
openGiftAuctionInfoModal,
|
||||
openGiftAuctionAcquiredModal,
|
||||
} = getActions();
|
||||
|
||||
const isOpen = Boolean(modal?.isOpen);
|
||||
const renderingAuctionState = useCurrentOrPrev(auctionState);
|
||||
|
||||
const gift = renderingAuctionState?.gift;
|
||||
const state = renderingAuctionState?.state;
|
||||
const userState = renderingAuctionState?.userState;
|
||||
const isFinished = state?.type === 'finished';
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const handleClose = useLastCallback(() => closeGiftAuctionModal());
|
||||
|
||||
const handleLearnMoreClick = useLastCallback(() => {
|
||||
openGiftAuctionInfoModal({});
|
||||
});
|
||||
|
||||
const handleItemsBoughtClick = useLastCallback(() => {
|
||||
if (!gift) return;
|
||||
const giftSticker = getStickerFromGift(gift);
|
||||
openGiftAuctionAcquiredModal({ giftId: gift.id, giftTitle: gift.title, giftSticker });
|
||||
});
|
||||
|
||||
const handleJoinClick = useLastCallback(() => {
|
||||
if (!gift) return;
|
||||
closeGiftAuctionModal({ shouldKeepActiveAuction: true });
|
||||
setGiftModalSelectedGift({ gift });
|
||||
});
|
||||
|
||||
const header = useMemo(() => {
|
||||
if (!gift || !state) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const giftTitle = gift.title || lang('StarGift');
|
||||
const giftsPerRound = gift.giftsPerRound || 0;
|
||||
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<GiftItemStar gift={gift} hideBadge noClickable />
|
||||
<h1 className={styles.title}>
|
||||
{giftTitle}
|
||||
</h1>
|
||||
{isFinished ? (
|
||||
<span className={styles.finishedBadge}>{lang('GiftAuctionEnded')}</span>
|
||||
) : (
|
||||
<p className={styles.description}>
|
||||
{lang('GiftAuctionTopBidders', {
|
||||
count: giftsPerRound,
|
||||
gift: <span className={styles.giftName}>{giftTitle}</span>,
|
||||
link: <Link isPrimary onClick={handleLearnMoreClick}>{lang('GiftAuctionLearnMore')}</Link>,
|
||||
}, { pluralValue: giftsPerRound, withNodes: true, withMarkdown: true })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, [gift, state, isFinished, lang, handleLearnMoreClick]);
|
||||
|
||||
const modalData = useMemo(() => {
|
||||
if (!gift || !state || !userState) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tableData: TableData = [];
|
||||
|
||||
tableData.push([
|
||||
lang('GiftAuctionStarted'),
|
||||
formatDateTimeToString(state.startDate * 1000, lang.code, true),
|
||||
]);
|
||||
|
||||
tableData.push([
|
||||
lang('GiftAuctionEnds'),
|
||||
formatDateTimeToString(state.endDate * 1000, lang.code, true),
|
||||
]);
|
||||
|
||||
if (gift.availabilityTotal) {
|
||||
tableData.push([
|
||||
lang('GiftInfoAvailability'),
|
||||
lang('GiftInfoAvailabilityValue', {
|
||||
count: gift.availabilityRemains || 0,
|
||||
total: lang.number(gift.availabilityTotal),
|
||||
}, { pluralValue: gift.availabilityRemains || 0 }),
|
||||
]);
|
||||
}
|
||||
|
||||
if (state.type === 'active') {
|
||||
tableData.push([
|
||||
lang('GiftAuctionCurrentRound'),
|
||||
lang('GiftAuctionRoundValue', {
|
||||
current: lang.number(state.currentRound),
|
||||
total: lang.number(state.totalRounds),
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
if (isFinished) {
|
||||
tableData.push([
|
||||
lang('GiftAuctionAveragePrice'),
|
||||
formatStarsAsIcon(lang, state.averagePrice, { className: styles.starIcon }),
|
||||
]);
|
||||
}
|
||||
|
||||
const acquiredCount = userState.acquiredCount;
|
||||
const giftSticker = getStickerFromGift(gift);
|
||||
const auctionTimeLeft = state.endDate - getServerTime();
|
||||
const shouldUseTextTimer = auctionTimeLeft > 0 && auctionTimeLeft < TEXT_TIMER_THRESHOLD;
|
||||
|
||||
const footer = (
|
||||
<div className={styles.footer}>
|
||||
{acquiredCount > 0 && (
|
||||
<Link className={styles.itemsBoughtLink} isPrimary onClick={handleItemsBoughtClick}>
|
||||
{lang('GiftAuctionItemsBought', {
|
||||
count: acquiredCount,
|
||||
gift: giftSticker && (
|
||||
<AnimatedIconFromSticker
|
||||
className={styles.itemsBoughtSticker}
|
||||
sticker={giftSticker}
|
||||
size={20}
|
||||
play={false}
|
||||
/>
|
||||
),
|
||||
}, { pluralValue: acquiredCount, withNodes: true })}
|
||||
</Link>
|
||||
)}
|
||||
<Button
|
||||
noForcedUpperCase
|
||||
className={styles.footerButton}
|
||||
onClick={isFinished ? handleClose : handleJoinClick}
|
||||
>
|
||||
{isFinished ? lang('OK') : (
|
||||
<div>
|
||||
<div>
|
||||
{lang('GiftAuctionJoin')}
|
||||
</div>
|
||||
{auctionTimeLeft > 0 && (
|
||||
<div className={styles.buttonSubtitle}>
|
||||
{lang('GiftAuctionTimeLeft', {
|
||||
time: shouldUseTextTimer
|
||||
? <TextTimer endsAt={state.endDate} />
|
||||
: formatCountdown(lang, auctionTimeLeft),
|
||||
}, { withNodes: true })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
return {
|
||||
tableData,
|
||||
footer,
|
||||
};
|
||||
}, [gift, state, userState, isFinished, lang, handleJoinClick, handleItemsBoughtClick, handleClose]);
|
||||
|
||||
return (
|
||||
<TableInfoModal
|
||||
isOpen={isOpen}
|
||||
header={header}
|
||||
footer={modalData?.footer}
|
||||
tableData={modalData?.tableData}
|
||||
className={styles.modal}
|
||||
contentClassName={styles.modalContent}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): Complete<StateProps> => {
|
||||
const { activeGiftAuction } = selectTabState(global);
|
||||
|
||||
return {
|
||||
auctionState: activeGiftAuction,
|
||||
};
|
||||
},
|
||||
)(GiftAuctionModal));
|
||||
@ -42,8 +42,8 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.soldOut {
|
||||
color: var(--color-error);
|
||||
.warningDescription {
|
||||
color: var(--color-error) !important;
|
||||
}
|
||||
|
||||
.modalContent {
|
||||
@ -51,18 +51,6 @@
|
||||
max-height: min(97vh, 48rem) !important;
|
||||
}
|
||||
|
||||
.headerSplitButton {
|
||||
position: absolute;
|
||||
right: 0.375rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
border-radius: 1rem;
|
||||
|
||||
backdrop-filter: blur(0.5rem);
|
||||
}
|
||||
|
||||
.moreMenuButton {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
|
||||
@ -440,9 +440,12 @@ const GiftInfoModal = ({
|
||||
|
||||
const isVisibleForMe = isNameHidden && renderingTargetPeer;
|
||||
|
||||
const isWarningDescription = savedGift?.isRefunded || (!savedGift && gift?.type === 'starGift');
|
||||
|
||||
const description = (() => {
|
||||
if (!savedGift) return lang('GiftInfoSoldOutDescription');
|
||||
if (isTargetChat) return undefined;
|
||||
if (savedGift.isRefunded) return lang('GiftInfoDescriptionRefunded');
|
||||
|
||||
if (savedGift.upgradeMsgId) return lang('GiftInfoDescriptionUpgraded');
|
||||
if (canManage && savedGift.canUpgrade && savedGift.alreadyPaidUpgradeStars && !savedGift.upgradeMsgId) {
|
||||
@ -592,7 +595,7 @@ const GiftInfoModal = ({
|
||||
{getTitle()}
|
||||
</h1>
|
||||
{Boolean(description) && (
|
||||
<p className={buildClassName(styles.description, !savedGift && gift?.type === 'starGift' && styles.soldOut)}>
|
||||
<p className={buildClassName(styles.description, isWarningDescription && styles.warningDescription)}>
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@ -5,10 +5,7 @@ import { getActions, withGlobal } from '../../../../global';
|
||||
|
||||
import type {
|
||||
ApiPeer,
|
||||
ApiStarGiftAttribute,
|
||||
ApiStarGiftAttributeBackdrop,
|
||||
ApiStarGiftAttributeModel,
|
||||
ApiStarGiftAttributePattern,
|
||||
} from '../../../../api/types';
|
||||
import type { TabState } from '../../../../global/types';
|
||||
import { ApiMediaFormat } from '../../../../api/types';
|
||||
@ -17,6 +14,7 @@ import { getStickerMediaHash } from '../../../../global/helpers';
|
||||
import { getPeerTitle } from '../../../../global/helpers/peers';
|
||||
import { selectPeer } from '../../../../global/selectors';
|
||||
import { fetch } from '../../../../util/mediaLoader';
|
||||
import { getRandomGiftPreviewAttributes, type GiftPreviewAttributes } from '../../../common/helpers/gifts';
|
||||
|
||||
import useInterval from '../../../../hooks/schedulers/useInterval';
|
||||
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||
@ -42,12 +40,6 @@ type StateProps = {
|
||||
recipient?: ApiPeer;
|
||||
};
|
||||
|
||||
type Attributes = {
|
||||
model: ApiStarGiftAttributeModel;
|
||||
pattern: ApiStarGiftAttributePattern;
|
||||
backdrop: ApiStarGiftAttributeBackdrop;
|
||||
};
|
||||
|
||||
const PREVIEW_UPDATE_INTERVAL = 3000;
|
||||
|
||||
const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
|
||||
@ -67,7 +59,7 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
|
||||
|
||||
const isPrepaid = Boolean(renderingModal?.gift?.prepaidUpgradeHash);
|
||||
|
||||
const [previewAttributes, setPreviewAttributes] = useState<Attributes | undefined>();
|
||||
const [previewAttributes, setPreviewAttributes] = useState<GiftPreviewAttributes | undefined>();
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
@ -111,7 +103,7 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
|
||||
|
||||
const updatePreviewAttributes = useLastCallback(() => {
|
||||
if (!renderingModal?.sampleAttributes) return;
|
||||
setPreviewAttributes(getRandomAttributes(renderingModal.sampleAttributes, previewAttributes));
|
||||
setPreviewAttributes(getRandomGiftPreviewAttributes(renderingModal.sampleAttributes, previewAttributes));
|
||||
});
|
||||
|
||||
const handleOpenPriceInfo = useLastCallback(() => {
|
||||
@ -274,25 +266,3 @@ export default memo(withGlobal<OwnProps>(
|
||||
};
|
||||
},
|
||||
)(GiftUpgradeModal));
|
||||
|
||||
function getRandomAttributes(list: ApiStarGiftAttribute[], previousSelection?: Attributes): Attributes {
|
||||
const models = list.filter((attr): attr is ApiStarGiftAttributeModel => (
|
||||
attr.type === 'model' && attr.name !== previousSelection?.model.name
|
||||
));
|
||||
const patterns = list.filter((attr): attr is ApiStarGiftAttributePattern => (
|
||||
attr.type === 'pattern' && attr.name !== previousSelection?.pattern.name
|
||||
));
|
||||
const backdrops = list.filter((attr): attr is ApiStarGiftAttributeBackdrop => (
|
||||
attr.type === 'backdrop' && attr.name !== previousSelection?.backdrop.name
|
||||
));
|
||||
|
||||
const randomModel = models[Math.floor(Math.random() * models.length)];
|
||||
const randomPattern = patterns[Math.floor(Math.random() * patterns.length)];
|
||||
const randomBackdrop = backdrops[Math.floor(Math.random() * backdrops.length)];
|
||||
|
||||
return {
|
||||
model: randomModel,
|
||||
pattern: randomPattern,
|
||||
backdrop: randomBackdrop,
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
@use "../../../styles/mixins";
|
||||
|
||||
/* stylelint-disable plugin/no-low-performance-animation-properties */
|
||||
|
||||
.root {
|
||||
--_size: 1.875rem;
|
||||
--_transition: 0.15s;
|
||||
--progress: 0;
|
||||
|
||||
position: relative;
|
||||
@ -9,6 +12,24 @@
|
||||
padding-top: 4rem;
|
||||
|
||||
@include mixins.reset-range();
|
||||
|
||||
&.dragging {
|
||||
.progress {
|
||||
transition: background-color var(--_transition);
|
||||
}
|
||||
|
||||
.floatingBadgeWrapper {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.floatingBadgeText {
|
||||
transition: background-color var(--_transition);
|
||||
}
|
||||
|
||||
.floatingBadgeTriangle {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slider {
|
||||
@ -79,6 +100,8 @@
|
||||
|
||||
background-image: var(--stars-gradient);
|
||||
|
||||
transition: width var(--_transition), background-color var(--_transition);
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
|
||||
@ -93,12 +116,14 @@
|
||||
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
&.dynamicColor {
|
||||
background-color: var(--dynamic-color);
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
|
||||
.floatingBadgeWrapper {
|
||||
--_min-x: 0;
|
||||
--_max-x: 100%;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
position: absolute;
|
||||
@ -107,11 +132,13 @@
|
||||
transform:
|
||||
translateX(
|
||||
clamp(
|
||||
var(--_min-x),
|
||||
var(--min-badge-x, 0px),
|
||||
calc(var(--_size) / 2 + var(--progress) * (100% - var(--_size))),
|
||||
var(--_max-x),
|
||||
var(--max-badge-x, 100%)
|
||||
)
|
||||
);
|
||||
|
||||
transition: transform var(--_transition);
|
||||
}
|
||||
|
||||
.floatingBadge {
|
||||
@ -121,12 +148,15 @@
|
||||
top: -1rem;
|
||||
left: 0;
|
||||
transform: translate(-50%, -100%);
|
||||
|
||||
&.clickable {
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.floatingBadgeText {
|
||||
display: flex;
|
||||
gap: 0.125rem;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 2rem;
|
||||
@ -138,6 +168,65 @@
|
||||
white-space: nowrap;
|
||||
|
||||
background-image: var(--stars-gradient);
|
||||
|
||||
transition:
|
||||
border-radius var(--_transition),
|
||||
width var(--_transition),
|
||||
background-color var(--_transition);
|
||||
|
||||
&.dynamicColor {
|
||||
background-color: var(--dynamic-color);
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
&.noTransition {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
.floatingBadgeSparkles {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.floatingBadgeContent {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 1.5rem;
|
||||
|
||||
&.withDescription {
|
||||
.floatingBadgeTitle {
|
||||
transform: translateY(-0.5rem);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.floatingBadgeDescription {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.floatingBadgeTitle {
|
||||
display: flex;
|
||||
gap: 0.125rem;
|
||||
align-items: center;
|
||||
transition: transform var(--_transition), font-size var(--_transition);
|
||||
}
|
||||
|
||||
.floatingBadgeDescription {
|
||||
position: absolute;
|
||||
bottom: -0.125rem;
|
||||
|
||||
font-size: 0.75rem;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity var(--_transition);
|
||||
}
|
||||
|
||||
.floatingBadgeTriangle {
|
||||
@ -145,5 +234,22 @@
|
||||
z-index: -1;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 33%);
|
||||
|
||||
transition: transform var(--_transition);
|
||||
}
|
||||
|
||||
.floatingBadgeTrianglePath {
|
||||
transition: fill var(--_transition);
|
||||
}
|
||||
|
||||
.customValueIcon {
|
||||
pointer-events: none;
|
||||
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
right: 0.375rem;
|
||||
bottom: 0.375rem;
|
||||
|
||||
font-size: 1.125rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
import type React from '../../../lib/teact/teact';
|
||||
import type { TeactNode } from '../../../lib/teact/teact';
|
||||
import {
|
||||
memo, useMemo, useRef, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
memo, useEffect,
|
||||
useMemo, useRef, useState } from '../../../lib/teact/teact';
|
||||
|
||||
import { requestMeasure, requestMutation } from '../../../lib/fasterdom/fasterdom';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatInteger } from '../../../util/textFormat';
|
||||
import buildStyle from '../../../util/buildStyle';
|
||||
import { REM } from '../../common/helpers/mediaDimensions';
|
||||
|
||||
import useEffectOnce from '../../../hooks/useEffectOnce';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import usePrevious from '../../../hooks/usePrevious';
|
||||
import useResizeObserver from '../../../hooks/useResizeObserver';
|
||||
|
||||
import AnimatedCounter from '../../common/AnimatedCounter';
|
||||
@ -20,87 +21,264 @@ import styles from './StarSlider.module.scss';
|
||||
type OwnProps = {
|
||||
maxValue: number;
|
||||
defaultValue: number;
|
||||
minValue?: number;
|
||||
minAllowedValue?: number;
|
||||
className?: string;
|
||||
floatingBadgeDescription?: TeactNode;
|
||||
shouldUseDynamicColor?: boolean;
|
||||
shouldAllowCustomValue?: boolean;
|
||||
onChange: (value: number) => void;
|
||||
onBadgeClick?: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
const DEFAULT_POINTS = [50, 100, 500, 1000, 2000, 5000, 10000];
|
||||
const LARGE_STEP = 10000;
|
||||
const THUMB_SIZE_IN_PIXELS = 1.875 * REM;
|
||||
const BEAK_WIDTH_IN_PIXELS = 28;
|
||||
const DEFAULT_RADIUS_IN_REM = 2;
|
||||
const MIN_RADIUS_IN_REM = 0.375;
|
||||
|
||||
const BADGE_HORIZONTAL_PADDING = 2 * REM;
|
||||
const BADGE_ICON_SIZE = 1.5 * REM;
|
||||
const BADGE_TITLE_GAP = 0.125 * REM;
|
||||
const DRAG_DISTANCE_THRESHOLD = 5;
|
||||
const BADGE_WIDTH_DELTA = 6;
|
||||
|
||||
let textMeasureCanvas: HTMLCanvasElement | undefined;
|
||||
|
||||
function getTextWidth(text: string, font: string): number {
|
||||
if (!textMeasureCanvas) {
|
||||
textMeasureCanvas = document.createElement('canvas');
|
||||
}
|
||||
const ctx = textMeasureCanvas.getContext('2d')!;
|
||||
ctx.font = font;
|
||||
return ctx.measureText(text).width;
|
||||
}
|
||||
|
||||
const SLIDER_COLORS = [
|
||||
'#955CDB', // Purple
|
||||
'#955CDB', // Purple
|
||||
'#46A3EB', // Blue
|
||||
'#40A920', // Green
|
||||
'#E29A09', // Yellow
|
||||
'#ED771E', // Orange
|
||||
'#E14542', // Red
|
||||
'#596473', // Silver (100% only)
|
||||
];
|
||||
|
||||
function getColorForProgress(progress: number): string {
|
||||
if (progress >= 1) return SLIDER_COLORS[SLIDER_COLORS.length - 1];
|
||||
|
||||
const regularColorsCount = SLIDER_COLORS.length - 1;
|
||||
const index = Math.floor(progress * regularColorsCount);
|
||||
return SLIDER_COLORS[Math.min(index, regularColorsCount - 1)];
|
||||
}
|
||||
|
||||
const StarSlider = ({
|
||||
maxValue,
|
||||
defaultValue,
|
||||
minValue,
|
||||
minAllowedValue,
|
||||
className,
|
||||
floatingBadgeDescription,
|
||||
shouldUseDynamicColor,
|
||||
shouldAllowCustomValue,
|
||||
onChange,
|
||||
onBadgeClick,
|
||||
}: OwnProps) => {
|
||||
const floatingBadgeRef = useRef<HTMLDivElement>();
|
||||
const containerRef = useRef<HTMLDivElement>();
|
||||
const floatingBadgeContentRef = useRef<HTMLDivElement>();
|
||||
const lang = useLang();
|
||||
|
||||
const min = minValue ?? 1;
|
||||
|
||||
const points = useMemo(() => {
|
||||
const result = [];
|
||||
|
||||
for (let i = 0; i < DEFAULT_POINTS.length; i++) {
|
||||
if (DEFAULT_POINTS[i] <= min) continue;
|
||||
|
||||
if (DEFAULT_POINTS[i] < maxValue) {
|
||||
result.push(DEFAULT_POINTS[i]);
|
||||
}
|
||||
|
||||
if (DEFAULT_POINTS[i] >= maxValue) {
|
||||
result.push(maxValue);
|
||||
break;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
const lastPoint = DEFAULT_POINTS[DEFAULT_POINTS.length - 1];
|
||||
let nextPoint = lastPoint + LARGE_STEP;
|
||||
while (nextPoint < maxValue) {
|
||||
result.push(nextPoint);
|
||||
nextPoint += LARGE_STEP;
|
||||
}
|
||||
result.push(maxValue);
|
||||
|
||||
return result;
|
||||
}, [maxValue]);
|
||||
}, [maxValue, min]);
|
||||
|
||||
const [value, setValue] = useState(0);
|
||||
const [containerWidth, setContainerWidth] = useState(0);
|
||||
const [badgeWidth, setBadgeWidth] = useState(0);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const startXRef = useRef<number | undefined>();
|
||||
const prevBadgeWidth = usePrevious(badgeWidth);
|
||||
|
||||
useEffectOnce(() => {
|
||||
setValue(getProgress(points, defaultValue));
|
||||
});
|
||||
const badgeText = lang.number(getValue(points, value, min));
|
||||
|
||||
const updateSafeBadgePosition = useLastCallback(() => {
|
||||
const badge = floatingBadgeRef.current;
|
||||
if (!badge) return;
|
||||
const parent = badge.parentElement!;
|
||||
const minAllowedProgress = minAllowedValue !== undefined
|
||||
? getProgress(points, minAllowedValue, min) : 0;
|
||||
|
||||
requestMeasure(() => {
|
||||
const safeMinX = parent.offsetLeft + badge.offsetWidth / 2;
|
||||
const safeMaxX = parent.offsetLeft + parent.offsetWidth - badge.offsetWidth / 2;
|
||||
useEffect(() => {
|
||||
setValue(getProgress(points, defaultValue, min));
|
||||
}, [defaultValue, points, min]);
|
||||
|
||||
requestMutation(() => {
|
||||
parent.style.setProperty('--_min-x', `${safeMinX}px`);
|
||||
parent.style.setProperty('--_max-x', `${safeMaxX}px`);
|
||||
});
|
||||
useEffect(() => {
|
||||
if (!floatingBadgeContentRef.current) return;
|
||||
|
||||
const titleEl = floatingBadgeContentRef.current.querySelector(`.${styles.floatingBadgeTitle}`);
|
||||
if (!titleEl) return;
|
||||
|
||||
const computedStyle = getComputedStyle(titleEl);
|
||||
const font = `${computedStyle.fontWeight} ${computedStyle.fontSize} ${computedStyle.fontFamily}`;
|
||||
|
||||
const textWidth = getTextWidth(badgeText, font);
|
||||
const titleWidth = BADGE_ICON_SIZE + BADGE_TITLE_GAP + textWidth;
|
||||
|
||||
const descriptionEl = floatingBadgeContentRef.current.querySelector(`.${styles.floatingBadgeDescription}`);
|
||||
const descriptionWidth = descriptionEl?.scrollWidth || 0;
|
||||
|
||||
const contentWidth = Math.max(titleWidth, descriptionWidth);
|
||||
const newBadgeWidth = contentWidth + BADGE_HORIZONTAL_PADDING;
|
||||
|
||||
setBadgeWidth((currentWidth) => {
|
||||
if (Math.abs(newBadgeWidth - currentWidth) < BADGE_WIDTH_DELTA) {
|
||||
return currentWidth;
|
||||
}
|
||||
return newBadgeWidth;
|
||||
});
|
||||
}, [badgeText, floatingBadgeDescription]);
|
||||
|
||||
const handleContainerResize = useLastCallback((entry: ResizeObserverEntry) => {
|
||||
setContainerWidth(entry.contentRect.width);
|
||||
});
|
||||
|
||||
useResizeObserver(floatingBadgeRef, updateSafeBadgePosition);
|
||||
useResizeObserver(containerRef, handleContainerResize);
|
||||
|
||||
const progress = value / points.length;
|
||||
const {
|
||||
minBadgeX, maxBadgeX, beakOffset, cornerRadius,
|
||||
} = useMemo(() => {
|
||||
return calcBadgePosition(containerWidth, badgeWidth, progress);
|
||||
}, [containerWidth, badgeWidth, progress]);
|
||||
|
||||
const handleChange = useLastCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = Number(event.currentTarget.value);
|
||||
setValue(newValue);
|
||||
const rawValue = Number(event.currentTarget.value);
|
||||
const clampedValue = Math.max(rawValue, minAllowedProgress);
|
||||
setValue(clampedValue);
|
||||
|
||||
onChange(getValue(points, newValue));
|
||||
const resultValue = getValue(points, clampedValue, min);
|
||||
onChange(resultValue);
|
||||
});
|
||||
|
||||
const handlePointerDown = useLastCallback((e: React.PointerEvent<HTMLInputElement>) => {
|
||||
startXRef.current = e.clientX;
|
||||
setIsDragging(false);
|
||||
});
|
||||
|
||||
const handlePointerMove = useLastCallback((e: React.PointerEvent<HTMLInputElement>) => {
|
||||
if (startXRef.current === undefined) return;
|
||||
const distance = Math.abs(e.clientX - startXRef.current);
|
||||
if (distance >= DRAG_DISTANCE_THRESHOLD) {
|
||||
setIsDragging(true);
|
||||
}
|
||||
});
|
||||
|
||||
const handlePointerUp = useLastCallback(() => {
|
||||
startXRef.current = undefined;
|
||||
setIsDragging(false);
|
||||
});
|
||||
|
||||
const { left: radiusLeft, right: radiusRight } = cornerRadius;
|
||||
const dynamicColor = shouldUseDynamicColor ? getColorForProgress(progress) : undefined;
|
||||
|
||||
const badgeStyle = buildStyle(
|
||||
`border-radius: 2rem 2rem ${radiusRight}rem ${radiusLeft}rem`,
|
||||
Boolean(badgeWidth) && `width: ${badgeWidth}px`,
|
||||
);
|
||||
|
||||
const rootStyle = buildStyle(
|
||||
`--progress: ${progress}`,
|
||||
`--min-badge-x: ${minBadgeX}px`,
|
||||
`--max-badge-x: ${maxBadgeX}px`,
|
||||
dynamicColor && `--dynamic-color: ${dynamicColor}`,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={buildClassName(styles.root, className)} style={`--progress: ${value / points.length}`}>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={buildClassName(styles.root, isDragging && styles.dragging, className)}
|
||||
style={rootStyle}
|
||||
>
|
||||
<div className={styles.floatingBadgeWrapper}>
|
||||
<div className={styles.floatingBadge} ref={floatingBadgeRef}>
|
||||
<div className={styles.floatingBadgeText}>
|
||||
<Icon name="star" className={styles.floatingBadgeIcon} />
|
||||
<AnimatedCounter text={formatInteger(getValue(points, value))} />
|
||||
<div
|
||||
className={buildClassName(styles.floatingBadge, onBadgeClick && styles.clickable)}
|
||||
onClick={onBadgeClick}
|
||||
>
|
||||
<div
|
||||
className={buildClassName(
|
||||
styles.floatingBadgeText,
|
||||
shouldUseDynamicColor && styles.dynamicColor,
|
||||
(!prevBadgeWidth || prevBadgeWidth === 0) && styles.noTransition,
|
||||
)}
|
||||
style={badgeStyle}
|
||||
>
|
||||
<Sparkles preset="button" className={styles.floatingBadgeSparkles} />
|
||||
<div
|
||||
ref={floatingBadgeContentRef}
|
||||
className={buildClassName(
|
||||
styles.floatingBadgeContent,
|
||||
floatingBadgeDescription && styles.withDescription,
|
||||
)}
|
||||
>
|
||||
<div className={styles.floatingBadgeTitle}>
|
||||
<Icon name="star" className={styles.floatingBadgeIcon} />
|
||||
<AnimatedCounter text={badgeText} />
|
||||
</div>
|
||||
<div className={styles.floatingBadgeDescription}>
|
||||
{floatingBadgeDescription}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<svg className={styles.floatingBadgeTriangle} width="28" height="28" viewBox="0 0 28 28" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="StarBadgeTriangle" x1="0" x2="1" y1="0" y2="0">
|
||||
<stop offset="-50%" stop-color="#FFAA00" />
|
||||
<stop offset="150%" stop-color="#FFCD3A" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="m 28,4 v 9 c 0.0089,7.283278 -3.302215,5.319646 -6.750951,8.589815 l -5.8284,5.82843 c -0.781,0.78105 -2.0474,0.78104 -2.8284,0 L 6.7638083,21.589815 C 2.8288652,17.959047 0.04527024,20.332086 0,13 V 4 C 0,4 0.00150581,0.97697493 3,1 5.3786658,1.018266 22.594519,0.9142007 25,1 c 2.992326,0.1067311 3,3 3,3 z" fill="url(#StarBadgeTriangle)" />
|
||||
<svg
|
||||
className={styles.floatingBadgeTriangle}
|
||||
width="28"
|
||||
height="28"
|
||||
viewBox="0 0 28 28"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
role="presentation"
|
||||
style={`transform: translate(calc(-50% + ${beakOffset}px), 33%)`}
|
||||
>
|
||||
{!shouldUseDynamicColor && (
|
||||
<defs>
|
||||
<linearGradient id="StarBadgeTriangle" x1="0" x2="1" y1="0" y2="0">
|
||||
<stop offset="-50%" stop-color="#FFAA00" />
|
||||
<stop offset="150%" stop-color="#FFCD3A" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
)}
|
||||
<path
|
||||
className={styles.floatingBadgeTrianglePath}
|
||||
d="m 28,4 v 9 c 0.0089,7.283278 -3.302215,5.319646 -6.750951,8.589815 l -5.8284,5.82843 c -0.781,0.78105 -2.0474,0.78104 -2.8284,0 L 6.7638083,21.589815 C 2.8288652,17.959047 0.04527024,20.332086 0,13 V 4 C 0,4 0.00150581,0.97697493 3,1 5.3786658,1.018266 22.594519,0.9142007 25,1 c 2.992326,0.1067311 3,3 3,3 z"
|
||||
fill={dynamicColor || 'url(#StarBadgeTriangle)'}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.progress}>
|
||||
<div className={buildClassName(styles.progress, shouldUseDynamicColor && styles.dynamicColor)}>
|
||||
<Sparkles preset="progress" className={styles.sparkles} />
|
||||
</div>
|
||||
<input
|
||||
@ -108,28 +286,82 @@ const StarSlider = ({
|
||||
type="range"
|
||||
min={0}
|
||||
max={points.length}
|
||||
defaultValue={getProgress(points, defaultValue)}
|
||||
defaultValue={getProgress(points, defaultValue, min)}
|
||||
step="any"
|
||||
onChange={handleChange}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerUp={handlePointerUp}
|
||||
/>
|
||||
{shouldAllowCustomValue && (
|
||||
<Icon name="add" className={styles.customValueIcon} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function getProgress(points: number[], value: number) {
|
||||
function getProgress(points: number[], value: number, minValue: number) {
|
||||
const pointIndex = points.findIndex((point) => value <= point);
|
||||
const prevPoint = points[pointIndex - 1] || 1;
|
||||
const prevPoint = points[pointIndex - 1] || minValue;
|
||||
const nextPoint = points[pointIndex] || points[points.length - 1];
|
||||
if (nextPoint === prevPoint) return pointIndex;
|
||||
const progress = (value - prevPoint) / (nextPoint - prevPoint);
|
||||
return pointIndex + progress;
|
||||
}
|
||||
|
||||
function getValue(points: number[], progress: number) {
|
||||
function getValue(points: number[], progress: number, minValue: number) {
|
||||
const pointIndex = Math.floor(progress);
|
||||
const prevPoint = points[pointIndex - 1] || 1;
|
||||
const prevPoint = points[pointIndex - 1] || minValue;
|
||||
const nextPoint = points[pointIndex] || points[points.length - 1];
|
||||
const value = prevPoint + (nextPoint - prevPoint) * (progress - pointIndex);
|
||||
return Math.round(value);
|
||||
}
|
||||
|
||||
function calcBadgePosition(
|
||||
containerWidth: number,
|
||||
badgeWidth: number,
|
||||
progress: number,
|
||||
) {
|
||||
const halfBadgeWidth = badgeWidth / 2;
|
||||
const halfThumbSize = THUMB_SIZE_IN_PIXELS / 2;
|
||||
|
||||
const baseTargetX = halfThumbSize + progress * (containerWidth - THUMB_SIZE_IN_PIXELS);
|
||||
const cornerTargetX = progress * containerWidth;
|
||||
|
||||
const edgeZone = THUMB_SIZE_IN_PIXELS / 2;
|
||||
const distanceToLeftEdge = cornerTargetX;
|
||||
const distanceToRightEdge = containerWidth - cornerTargetX;
|
||||
const minEdgeDistance = Math.min(distanceToLeftEdge, distanceToRightEdge);
|
||||
|
||||
const t = Math.min(1, minEdgeDistance / edgeZone);
|
||||
const targetX = cornerTargetX + t * (baseTargetX - cornerTargetX);
|
||||
const minBadgeX = halfBadgeWidth;
|
||||
const maxBadgeX = containerWidth - halfBadgeWidth;
|
||||
const clampedBadgeX = Math.max(minBadgeX, Math.min(targetX, maxBadgeX));
|
||||
|
||||
const beakOffset = targetX - clampedBadgeX;
|
||||
|
||||
const thresholdPx = DEFAULT_RADIUS_IN_REM / 2 * REM;
|
||||
const beakHalfWidth = BEAK_WIDTH_IN_PIXELS / 2;
|
||||
|
||||
const distanceToEdge = halfBadgeWidth - Math.abs(beakOffset);
|
||||
const normalizedDistance = Math.max(0, distanceToEdge - beakHalfWidth);
|
||||
|
||||
let edgeRadius = DEFAULT_RADIUS_IN_REM;
|
||||
if (normalizedDistance < thresholdPx) {
|
||||
const radiusT = 1 - (normalizedDistance / thresholdPx);
|
||||
edgeRadius = DEFAULT_RADIUS_IN_REM - radiusT * (DEFAULT_RADIUS_IN_REM - MIN_RADIUS_IN_REM);
|
||||
}
|
||||
|
||||
const leftRadius = beakOffset < 0 ? edgeRadius : DEFAULT_RADIUS_IN_REM;
|
||||
const rightRadius = beakOffset > 0 ? edgeRadius : DEFAULT_RADIUS_IN_REM;
|
||||
|
||||
return {
|
||||
minBadgeX,
|
||||
maxBadgeX,
|
||||
beakOffset,
|
||||
cornerRadius: { left: leftRadius, right: rightRadius },
|
||||
};
|
||||
}
|
||||
|
||||
export default memo(StarSlider);
|
||||
|
||||
@ -37,6 +37,12 @@ export function getTransactionTitle(oldLang: OldLangFn, lang: LangFn, transactio
|
||||
return lang('GiftPrepaidUpgradeTransactionTitle');
|
||||
}
|
||||
|
||||
if (transaction.isStarGiftAuctionBid) {
|
||||
return isNegativeAmount(transaction.amount)
|
||||
? lang('StarGiftAuctionBidTransaction')
|
||||
: lang('StarGiftAuctionBidRefundedTransaction');
|
||||
}
|
||||
|
||||
if (transaction.starRefCommision) {
|
||||
return oldLang('StarTransactionCommission', formatPercent(transaction.starRefCommision));
|
||||
}
|
||||
|
||||
@ -260,7 +260,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
peerLabel = oldLang('Stars.Transaction.Via');
|
||||
}
|
||||
|
||||
if (!transaction.isPostsSearch && !isDropOriginalDetails) {
|
||||
if (!transaction.isPostsSearch && !isDropOriginalDetails && !transaction.isStarGiftAuctionBid) {
|
||||
tableData.push([
|
||||
peerLabel,
|
||||
peerId ? { chatId: peerId } : toName || '',
|
||||
@ -312,6 +312,18 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
formatDateTimeToString(transaction.date * 1000, oldLang.code, true),
|
||||
]);
|
||||
|
||||
if (transaction.isStarGiftAuctionBid && gift?.type === 'starGift' && gift.availabilityTotal) {
|
||||
tableData.push([
|
||||
lang('GiftInfoAvailability'),
|
||||
lang('GiftInfoAvailabilityValue', {
|
||||
count: gift.availabilityRemains || 0,
|
||||
total: gift.availabilityTotal,
|
||||
}, {
|
||||
pluralValue: gift.availabilityRemains || 0,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
const footerText = oldLang('lng_credits_box_out_about');
|
||||
const footerTextParts = footerText.split('{link}');
|
||||
|
||||
|
||||
@ -10,12 +10,13 @@ import AnimatedCounter from '../common/AnimatedCounter';
|
||||
|
||||
type OwnProps = {
|
||||
endsAt: number;
|
||||
shouldShowZeroOnEnd?: boolean;
|
||||
onEnd?: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
const UPDATE_FREQUENCY = 500; // Sometimes second gets skipped if using 1000
|
||||
|
||||
const TextTimer = ({ endsAt, onEnd }: OwnProps) => {
|
||||
const TextTimer = ({ endsAt, shouldShowZeroOnEnd, onEnd }: OwnProps) => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const serverTime = getServerTime();
|
||||
@ -28,9 +29,9 @@ const TextTimer = ({ endsAt, onEnd }: OwnProps) => {
|
||||
}
|
||||
}, [isActive, onEnd]);
|
||||
|
||||
if (!isActive) return undefined;
|
||||
if (!isActive && !shouldShowZeroOnEnd) return undefined;
|
||||
|
||||
const timeLeft = endsAt - serverTime;
|
||||
const timeLeft = Math.max(0, endsAt - serverTime);
|
||||
const time = formatMediaDuration(timeLeft);
|
||||
|
||||
const timeParts = time.split(':');
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type {
|
||||
ApiInputInvoice, ApiInputInvoicePremiumGiftStars, ApiInputInvoiceStarGift, ApiInputInvoiceStarGiftResale,
|
||||
ApiInputInvoice, ApiInputInvoicePremiumGiftStars, ApiInputInvoiceStarGift,
|
||||
ApiInputInvoiceStarGiftAuctionBid, ApiInputInvoiceStarGiftResale,
|
||||
ApiRequestInputInvoice,
|
||||
} from '../../../api/types';
|
||||
import type { ApiCredentials } from '../../../components/payment/PaymentModal';
|
||||
@ -575,7 +576,7 @@ addActionHandler('checkCanSendGift', async (global, actions, payload): Promise<v
|
||||
|
||||
addActionHandler('openGiftModal', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
forUserId, selectedResaleGift, tabId = getCurrentTabId(),
|
||||
forUserId, selectedGift, selectedResaleGift, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
if (selectIsCurrentUserFrozen(global)) {
|
||||
@ -592,6 +593,7 @@ addActionHandler('openGiftModal', async (global, actions, payload): Promise<void
|
||||
forPeerId: forUserId,
|
||||
gifts,
|
||||
selectedResaleGift,
|
||||
selectedGift,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
@ -1140,6 +1142,24 @@ addActionHandler('upgradePrepaidGift', (global, actions, payload): ActionReturnT
|
||||
payInputStarInvoice(global, invoice, stars, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('sendStarGiftAuctionBid', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
giftId, bidAmount, peerId, message, shouldHideName, isUpdateBid, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
const invoice: ApiInputInvoiceStarGiftAuctionBid = {
|
||||
type: 'stargiftAuctionBid',
|
||||
giftId,
|
||||
bidAmount,
|
||||
peerId,
|
||||
message,
|
||||
shouldHideName,
|
||||
isUpdateBid,
|
||||
};
|
||||
|
||||
payInputStarInvoice(global, invoice, bidAmount, tabId);
|
||||
});
|
||||
|
||||
async function payInputStarInvoice<T extends GlobalState>(
|
||||
global: T, inputInvoice: ApiInputInvoice, price: number,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
@ -1229,6 +1249,26 @@ addActionHandler('openUniqueGiftBySlug', async (global, actions, payload): Promi
|
||||
actions.openGiftInfoModal({ gift, tabId });
|
||||
});
|
||||
|
||||
addActionHandler('openGiftAuctionBySlug', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
slug, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
const auctionState = await callApi('fetchStarGiftAuctionState', { slug });
|
||||
|
||||
if (!auctionState) {
|
||||
actions.showNotification({
|
||||
message: {
|
||||
key: 'GiftWasNotFound',
|
||||
},
|
||||
tabId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
actions.openGiftAuctionModal({ gift: auctionState.gift, tabId });
|
||||
});
|
||||
|
||||
addActionHandler('processStarGiftWithdrawal', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
gift, password, tabId = getCurrentTabId(),
|
||||
|
||||
@ -12,11 +12,12 @@ import { getServerTime } from '../../../util/serverTime';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import { RESALE_GIFTS_LIMIT } from '../../../limits';
|
||||
import { areInputSavedGiftsEqual, getRequestInputSavedStarGift } from '../../helpers/payments';
|
||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||
import { addActionHandler, getGlobal, getPromiseActions, setGlobal } from '../../index';
|
||||
import {
|
||||
appendStarsSubscriptions,
|
||||
appendStarsTransactions,
|
||||
replacePeerSavedGifts,
|
||||
updateActiveGiftAuction,
|
||||
updateChats,
|
||||
updatePeerStarGiftCollections,
|
||||
updateStarsBalance,
|
||||
@ -579,6 +580,43 @@ addActionHandler('shiftGiftUpgradeNextPrice', async (global, _actions, payload):
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('openGiftAuctionModal', async (global, _actions, payload): Promise<void> => {
|
||||
const { gift, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
await getPromiseActions().loadActiveGiftAuction({ giftId: gift.id, tabId });
|
||||
|
||||
global = getGlobal();
|
||||
global = updateTabState(global, {
|
||||
giftAuctionModal: { isOpen: true },
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('loadActiveGiftAuction', async (global, _actions, payload): Promise<void> => {
|
||||
const { giftId, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const currentAuction = selectTabState(global, tabId).activeGiftAuction;
|
||||
const currentVersion = currentAuction?.state.type === 'active' ? currentAuction.state.version : 0;
|
||||
|
||||
const auctionState = await callApi('fetchStarGiftAuctionState', {
|
||||
giftId,
|
||||
version: currentVersion,
|
||||
});
|
||||
if (!auctionState) return;
|
||||
|
||||
global = getGlobal();
|
||||
global = updateActiveGiftAuction(global, auctionState, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('clearActiveGiftAuction', (global, _actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
activeGiftAuction: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('toggleSavedGiftPinned', async (global, actions, payload): Promise<void> => {
|
||||
const { gift, peerId, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
@ -650,3 +688,26 @@ addActionHandler('loadStarGiftCollections', async (global, actions, payload): Pr
|
||||
global = updatePeerStarGiftCollections(global, peerId, result.collections);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('openGiftAuctionAcquiredModal', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
giftId, giftTitle, giftSticker, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
const result = await callApi('fetchStarGiftAuctionAcquiredGifts', { giftId });
|
||||
|
||||
if (!result) return;
|
||||
|
||||
global = getGlobal();
|
||||
|
||||
global = updateTabState(global, {
|
||||
giftAuctionAcquiredModal: {
|
||||
giftId,
|
||||
giftTitle,
|
||||
giftSticker,
|
||||
acquiredGifts: result.gifts,
|
||||
},
|
||||
}, tabId);
|
||||
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
@ -240,6 +240,40 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
|
||||
case 'newMessage': {
|
||||
const action = update.message.content?.action;
|
||||
|
||||
if (action?.type === 'starGift' && update.message.isOutgoing) {
|
||||
const { gift } = action;
|
||||
if (!gift.isAuction || update.message.chatId === SERVICE_NOTIFICATIONS_USER_ID) return undefined;
|
||||
|
||||
const { chatId, id } = update.message;
|
||||
|
||||
if (!chatId || !id) return;
|
||||
|
||||
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||
actions.focusMessage({
|
||||
chatId,
|
||||
messageId: id,
|
||||
tabId,
|
||||
});
|
||||
actions.closeGiftAuctionBidModal({ tabId });
|
||||
actions.closeGiftModal({ tabId });
|
||||
|
||||
actions.showNotification({
|
||||
icon: 'auction-filled',
|
||||
message: {
|
||||
key: 'GiftAuctionWonNotification',
|
||||
variables: {
|
||||
gift: gift.title,
|
||||
},
|
||||
},
|
||||
tabId,
|
||||
});
|
||||
|
||||
actions.requestConfetti({ withStars: true, tabId });
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!update.message.isOutgoing && update.message.chatId !== SERVICE_NOTIFICATIONS_USER_ID) return undefined;
|
||||
if (action?.type !== 'starGiftUnique') return undefined;
|
||||
const actionStarGift = action.gift;
|
||||
|
||||
@ -4,7 +4,12 @@ import { formatCurrencyAsString } from '../../../util/formatCurrency';
|
||||
import * as langProvider from '../../../util/oldLangProvider';
|
||||
import { getPeerTitle } from '../../helpers/peers';
|
||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||
import { removeGiftInfoOriginalDetails, updateStarsBalance } from '../../reducers';
|
||||
import {
|
||||
removeGiftInfoOriginalDetails,
|
||||
updateActiveGiftAuctionState,
|
||||
updateActiveGiftAuctionUserState,
|
||||
updateStarsBalance,
|
||||
} from '../../reducers';
|
||||
import { updateTabState } from '../../reducers/tabs';
|
||||
import { selectPeer, selectTabState } from '../../selectors';
|
||||
|
||||
@ -210,6 +215,27 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
});
|
||||
}
|
||||
|
||||
if (inputInvoice?.type === 'stargiftAuctionBid') {
|
||||
const { activeGiftAuction } = selectTabState(global, tabId);
|
||||
const giftsPerRound = activeGiftAuction?.gift.giftsPerRound;
|
||||
|
||||
actions.showNotification({
|
||||
icon: 'auction-filled',
|
||||
title: {
|
||||
key: inputInvoice.isUpdateBid ? 'GiftAuctionBidIncreasedTitle' : 'GiftAuctionBidPlacedTitle',
|
||||
},
|
||||
message: {
|
||||
key: 'GiftAuctionBidPlacedMessage',
|
||||
variables: { count: giftsPerRound },
|
||||
},
|
||||
tabId,
|
||||
});
|
||||
|
||||
if (activeGiftAuction?.gift.id === inputInvoice.giftId) {
|
||||
actions.loadActiveGiftAuction({ giftId: inputInvoice.giftId, tabId });
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@ -221,5 +247,29 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
actions.loadStarStatus();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'updateStarGiftAuctionState': {
|
||||
const { giftId, state } = update;
|
||||
|
||||
Object.keys(global.byTabId).forEach((tabIdStr) => {
|
||||
const tabId = Number(tabIdStr);
|
||||
global = updateActiveGiftAuctionState(global, giftId, state, tabId);
|
||||
});
|
||||
|
||||
setGlobal(global);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'updateStarGiftAuctionUserState': {
|
||||
const { giftId, userState } = update;
|
||||
|
||||
Object.keys(global.byTabId).forEach((tabIdStr) => {
|
||||
const tabId = Number(tabIdStr);
|
||||
global = updateActiveGiftAuctionUserState(global, giftId, userState, tabId);
|
||||
});
|
||||
|
||||
setGlobal(global);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -11,8 +11,7 @@ import { callApi } from '../../../api/gramjs';
|
||||
import { addTabStateResetterAction } from '../../helpers/meta';
|
||||
import { getPrizeStarsTransactionFromGiveaway, getStarsTransactionFromGift } from '../../helpers/payments';
|
||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||
import {
|
||||
clearStarPayment, openStarsTransactionModal,
|
||||
import { clearStarPayment, openStarsTransactionModal,
|
||||
} from '../../reducers';
|
||||
import { updateTabState } from '../../reducers/tabs';
|
||||
import {
|
||||
@ -206,6 +205,31 @@ addTabStateResetterAction('closeStarsSubscriptionModal', 'starsSubscriptionModal
|
||||
|
||||
addTabStateResetterAction('closeGiftModal', 'giftModal');
|
||||
|
||||
addActionHandler('setGiftModalSelectedGift', (global, actions, payload): ActionReturnType => {
|
||||
const { gift, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const tabState = selectTabState(global, tabId);
|
||||
const giftModal = tabState?.giftModal;
|
||||
if (giftModal) {
|
||||
return updateTabState(global, {
|
||||
giftModal: {
|
||||
...giftModal,
|
||||
selectedGift: gift,
|
||||
},
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
if (gift && 'id' in gift) {
|
||||
actions.openGiftModal({
|
||||
forUserId: global.currentUserId!,
|
||||
selectedGift: gift,
|
||||
tabId,
|
||||
});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
addActionHandler('closeStarsGiftModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
return updateTabState(global, {
|
||||
@ -387,6 +411,52 @@ addTabStateResetterAction('closeGiftResalePriceComposerModal', 'giftResalePriceC
|
||||
|
||||
addTabStateResetterAction('closeGiftUpgradeModal', 'giftUpgradeModal');
|
||||
|
||||
addActionHandler('closeGiftAuctionModal', (global, _actions, payload): ActionReturnType => {
|
||||
const { shouldKeepActiveAuction, tabId = getCurrentTabId() } = payload || {};
|
||||
const tabState = selectTabState(global, tabId);
|
||||
|
||||
return updateTabState(global, {
|
||||
giftAuctionModal: undefined,
|
||||
activeGiftAuction: shouldKeepActiveAuction ? tabState?.activeGiftAuction : undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('openGiftAuctionBidModal', (global, _actions, payload): ActionReturnType => {
|
||||
const { peerId, message, shouldHideName, tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
giftAuctionBidModal: { isOpen: true, peerId, message, shouldHideName },
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addTabStateResetterAction('closeGiftAuctionBidModal', 'giftAuctionBidModal');
|
||||
|
||||
addActionHandler('openGiftAuctionInfoModal', (global, _actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
giftAuctionInfoModal: { isOpen: true },
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addTabStateResetterAction('closeGiftAuctionInfoModal', 'giftAuctionInfoModal');
|
||||
|
||||
addActionHandler('openGiftAuctionChangeRecipientModal', (global, _actions, payload): ActionReturnType => {
|
||||
const {
|
||||
oldPeerId, newPeerId, message, shouldHideName, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
return updateTabState(global, {
|
||||
giftAuctionChangeRecipientModal: {
|
||||
isOpen: true, oldPeerId, newPeerId, message, shouldHideName,
|
||||
},
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addTabStateResetterAction('closeGiftAuctionChangeRecipientModal', 'giftAuctionChangeRecipientModal');
|
||||
|
||||
addTabStateResetterAction('closeGiftAuctionAcquiredModal', 'giftAuctionAcquiredModal');
|
||||
|
||||
addActionHandler('openStarGiftPriceDecreaseInfoModal', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
prices, currentPrice, minPrice, maxPrice, tabId = getCurrentTabId(),
|
||||
|
||||
@ -261,6 +261,23 @@ export function getRequestInputInvoice<T extends GlobalState>(
|
||||
};
|
||||
}
|
||||
|
||||
if (inputInvoice.type === 'stargiftAuctionBid') {
|
||||
const {
|
||||
giftId, bidAmount, peerId, message, shouldHideName, isUpdateBid,
|
||||
} = inputInvoice;
|
||||
const peer = peerId ? selectPeer(global, peerId) : undefined;
|
||||
|
||||
return {
|
||||
type: 'stargiftAuctionBid',
|
||||
giftId,
|
||||
bidAmount,
|
||||
peer,
|
||||
message,
|
||||
shouldHideName,
|
||||
isUpdateBid,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,13 @@
|
||||
import type { ApiSavedStarGift } from '../../api/types';
|
||||
import type {
|
||||
ApiSavedStarGift,
|
||||
ApiStarGiftAuctionState,
|
||||
ApiStarGiftAuctionUserState,
|
||||
ApiTypeStarGiftAuctionState,
|
||||
} from '../../api/types';
|
||||
import type { GlobalState } from '../types';
|
||||
|
||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||
import { selectTabState } from '../selectors';
|
||||
import { updateTabState } from './tabs';
|
||||
|
||||
export function removeGiftInfoOriginalDetails<T extends GlobalState>(
|
||||
@ -46,3 +52,73 @@ export function removeGiftInfoOriginalDetails<T extends GlobalState>(
|
||||
},
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
function getAuctionStateVersion(state: ApiTypeStarGiftAuctionState): number {
|
||||
return state.type === 'active' ? state.version : 0;
|
||||
}
|
||||
|
||||
export function updateActiveGiftAuction<T extends GlobalState>(
|
||||
global: T,
|
||||
auctionState: ApiStarGiftAuctionState,
|
||||
tabId: number = getCurrentTabId(),
|
||||
): T {
|
||||
const currentAuction = selectTabState(global, tabId).activeGiftAuction;
|
||||
|
||||
const serverVersion = getAuctionStateVersion(auctionState.state);
|
||||
const clientVersion = currentAuction ? getAuctionStateVersion(currentAuction.state) : -1;
|
||||
|
||||
if (serverVersion >= clientVersion) {
|
||||
return updateTabState(global, {
|
||||
activeGiftAuction: auctionState,
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
return global;
|
||||
}
|
||||
|
||||
export function updateActiveGiftAuctionState<T extends GlobalState>(
|
||||
global: T,
|
||||
giftId: string,
|
||||
state: ApiTypeStarGiftAuctionState,
|
||||
tabId: number,
|
||||
): T {
|
||||
const activeAuction = selectTabState(global, tabId).activeGiftAuction;
|
||||
|
||||
if (!activeAuction || activeAuction.gift.id !== giftId) {
|
||||
return global;
|
||||
}
|
||||
|
||||
const serverVersion = getAuctionStateVersion(state);
|
||||
const clientVersion = getAuctionStateVersion(activeAuction.state);
|
||||
|
||||
if (serverVersion > clientVersion) {
|
||||
return updateTabState(global, {
|
||||
activeGiftAuction: {
|
||||
...activeAuction,
|
||||
state,
|
||||
},
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
return global;
|
||||
}
|
||||
|
||||
export function updateActiveGiftAuctionUserState<T extends GlobalState>(
|
||||
global: T,
|
||||
giftId: string,
|
||||
userState: ApiStarGiftAuctionUserState,
|
||||
tabId: number,
|
||||
): T {
|
||||
const activeAuction = selectTabState(global, tabId).activeGiftAuction;
|
||||
|
||||
if (!activeAuction || activeAuction.gift.id !== giftId) {
|
||||
return global;
|
||||
}
|
||||
|
||||
return updateTabState(global, {
|
||||
activeGiftAuction: {
|
||||
...activeAuction,
|
||||
userState,
|
||||
},
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
@ -33,6 +33,7 @@ import type {
|
||||
ApiPaymentStatus,
|
||||
ApiPeer,
|
||||
ApiPhoto,
|
||||
ApiPremiumGiftCodeOption,
|
||||
ApiPremiumSection,
|
||||
ApiPreparedInlineMessage,
|
||||
ApiPrivacyKey,
|
||||
@ -46,6 +47,7 @@ import type {
|
||||
ApiSessionData,
|
||||
ApiStarGift,
|
||||
ApiStarGiftAttributeOriginalDetails,
|
||||
ApiStarGiftRegular,
|
||||
ApiStarGiftUnique,
|
||||
ApiStarGiftUpgradePrice,
|
||||
ApiStarsSubscription,
|
||||
@ -1653,6 +1655,9 @@ export interface ActionPayloads {
|
||||
openUniqueGiftBySlug: {
|
||||
slug: string;
|
||||
} & WithTabId;
|
||||
openGiftAuctionBySlug: {
|
||||
slug: string;
|
||||
} & WithTabId;
|
||||
openPreviousStory: WithTabId | undefined;
|
||||
openNextStory: WithTabId | undefined;
|
||||
setStoryViewerMuted: {
|
||||
@ -2600,9 +2605,13 @@ export interface ActionPayloads {
|
||||
|
||||
openGiftModal: {
|
||||
forUserId: string;
|
||||
selectedGift?: ApiStarGift;
|
||||
selectedResaleGift?: ApiStarGift;
|
||||
} & WithTabId;
|
||||
closeGiftModal: WithTabId | undefined;
|
||||
setGiftModalSelectedGift: {
|
||||
gift: ApiPremiumGiftCodeOption | ApiStarGift | undefined;
|
||||
} & WithTabId;
|
||||
sendStarGift: StarGiftInfo & WithTabId;
|
||||
buyStarGift: {
|
||||
peerId: string;
|
||||
@ -2686,6 +2695,45 @@ export interface ActionPayloads {
|
||||
gift: ApiStarGiftUnique;
|
||||
} & WithTabId;
|
||||
closeGiftInfoValueModal: WithTabId | undefined;
|
||||
openGiftAuctionModal: {
|
||||
gift: ApiStarGiftRegular;
|
||||
} & WithTabId;
|
||||
closeGiftAuctionModal: {
|
||||
shouldKeepActiveAuction?: boolean;
|
||||
} & WithTabId | undefined;
|
||||
openGiftAuctionBidModal: {
|
||||
peerId?: string;
|
||||
message?: string;
|
||||
shouldHideName?: boolean;
|
||||
} & WithTabId | undefined;
|
||||
closeGiftAuctionBidModal: WithTabId | undefined;
|
||||
openGiftAuctionInfoModal: WithTabId | undefined;
|
||||
closeGiftAuctionInfoModal: WithTabId | undefined;
|
||||
openGiftAuctionChangeRecipientModal: {
|
||||
oldPeerId: string;
|
||||
newPeerId: string;
|
||||
message?: string;
|
||||
shouldHideName?: boolean;
|
||||
} & WithTabId;
|
||||
closeGiftAuctionChangeRecipientModal: WithTabId | undefined;
|
||||
openGiftAuctionAcquiredModal: {
|
||||
giftId: string;
|
||||
giftTitle?: string;
|
||||
giftSticker?: ApiSticker;
|
||||
} & WithTabId;
|
||||
closeGiftAuctionAcquiredModal: WithTabId | undefined;
|
||||
sendStarGiftAuctionBid: {
|
||||
giftId: string;
|
||||
bidAmount: number;
|
||||
peerId?: string;
|
||||
message?: ApiFormattedText;
|
||||
shouldHideName?: boolean;
|
||||
isUpdateBid?: boolean;
|
||||
} & WithTabId;
|
||||
loadActiveGiftAuction: {
|
||||
giftId: string;
|
||||
} & WithTabId;
|
||||
clearActiveGiftAuction: WithTabId | undefined;
|
||||
processStarGiftWithdrawal: {
|
||||
gift: ApiInputSavedStarGift;
|
||||
password: string;
|
||||
|
||||
@ -44,6 +44,8 @@ import type {
|
||||
ApiStarGiftAttribute,
|
||||
ApiStarGiftAttributeCounter,
|
||||
ApiStarGiftAttributeOriginalDetails,
|
||||
ApiStarGiftAuctionAcquiredGift,
|
||||
ApiStarGiftAuctionState,
|
||||
ApiStarGiftUnique,
|
||||
ApiStarGiftUpgradePrice,
|
||||
ApiStarGiveawayOption,
|
||||
@ -703,7 +705,9 @@ export type TabState = {
|
||||
forPeerId: string;
|
||||
gifts?: ApiPremiumGiftCodeOption[];
|
||||
selectedResaleGift?: ApiStarGift;
|
||||
selectedGift?: ApiPremiumGiftCodeOption | ApiStarGift;
|
||||
};
|
||||
activeGiftAuction?: ApiStarGiftAuctionState;
|
||||
chatRefundModal?: {
|
||||
userId: string;
|
||||
starsToRefund: number;
|
||||
@ -889,6 +893,36 @@ export type TabState = {
|
||||
emojiStatus: ApiEmojiStatusCollectible;
|
||||
};
|
||||
|
||||
giftAuctionModal?: {
|
||||
isOpen?: boolean;
|
||||
};
|
||||
|
||||
giftAuctionBidModal?: {
|
||||
isOpen?: boolean;
|
||||
peerId?: string;
|
||||
message?: string;
|
||||
shouldHideName?: boolean;
|
||||
};
|
||||
|
||||
giftAuctionInfoModal?: {
|
||||
isOpen?: boolean;
|
||||
};
|
||||
|
||||
giftAuctionChangeRecipientModal?: {
|
||||
isOpen?: boolean;
|
||||
oldPeerId?: string;
|
||||
newPeerId?: string;
|
||||
message?: string;
|
||||
shouldHideName?: boolean;
|
||||
};
|
||||
|
||||
giftAuctionAcquiredModal?: {
|
||||
giftId?: string;
|
||||
giftTitle?: string;
|
||||
giftSticker?: ApiSticker;
|
||||
acquiredGifts?: ApiStarGiftAuctionAcquiredGift[];
|
||||
};
|
||||
|
||||
starGiftPriceDecreaseInfoModal?: {
|
||||
prices: ApiStarGiftUpgradePrice[];
|
||||
currentPrice: number;
|
||||
|
||||
@ -12,10 +12,12 @@ const useScrollNotch = ({
|
||||
containerRef,
|
||||
selector,
|
||||
isBottomNotch,
|
||||
shouldHideTopNotch,
|
||||
}: {
|
||||
containerRef: ElementRef<HTMLDivElement>;
|
||||
selector: string;
|
||||
isBottomNotch?: boolean;
|
||||
shouldHideTopNotch?: boolean;
|
||||
}, deps: unknown[]) => {
|
||||
useLayoutEffect(() => {
|
||||
const elements = containerRef.current?.querySelectorAll<HTMLElement>(selector);
|
||||
@ -28,19 +30,21 @@ const useScrollNotch = ({
|
||||
const isAtEnd = scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD;
|
||||
|
||||
requestMutation(() => {
|
||||
if (!shouldHideTopNotch) {
|
||||
toggleExtraClass(target, 'scrolled', isScrolled);
|
||||
}
|
||||
if (isBottomNotch) {
|
||||
toggleExtraClass(target, 'scrolled-to-end', isAtEnd);
|
||||
} else {
|
||||
toggleExtraClass(target, 'scrolled', isScrolled);
|
||||
}
|
||||
});
|
||||
}, THROTTLE_DELAY);
|
||||
|
||||
elements.forEach((el) => {
|
||||
if (!shouldHideTopNotch) {
|
||||
addExtraClass(el, 'with-notch');
|
||||
}
|
||||
if (isBottomNotch) {
|
||||
addExtraClass(el, 'with-bottom-notch');
|
||||
} else {
|
||||
addExtraClass(el, 'with-notch');
|
||||
}
|
||||
el.addEventListener('scroll', handleScroll, { passive: true });
|
||||
});
|
||||
@ -55,7 +59,7 @@ const useScrollNotch = ({
|
||||
});
|
||||
};
|
||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps
|
||||
}, [containerRef, selector, isBottomNotch, ...deps]);
|
||||
}, [containerRef, selector, isBottomNotch, shouldHideTopNotch, ...deps]);
|
||||
|
||||
useEffect(() => {
|
||||
const elements = containerRef.current?.querySelectorAll<HTMLElement>(selector);
|
||||
@ -67,15 +71,16 @@ const useScrollNotch = ({
|
||||
const isAtEnd = scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD;
|
||||
|
||||
requestMutation(() => {
|
||||
if (!shouldHideTopNotch) {
|
||||
toggleExtraClass(el, 'scrolled', isScrolled);
|
||||
}
|
||||
if (isBottomNotch) {
|
||||
toggleExtraClass(el, 'scrolled-to-end', isAtEnd);
|
||||
} else {
|
||||
toggleExtraClass(el, 'scrolled', isScrolled);
|
||||
}
|
||||
});
|
||||
});
|
||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps
|
||||
}, [containerRef, selector, isBottomNotch, ...deps]);
|
||||
}, [containerRef, selector, isBottomNotch, shouldHideTopNotch, ...deps]);
|
||||
};
|
||||
|
||||
export default useScrollNotch;
|
||||
|
||||
@ -1881,6 +1881,8 @@ payments.updateStarGiftPrice#edbe6ccb stargift:InputSavedStarGift resell_amount:
|
||||
payments.getStarGiftCollections#981b91dd peer:InputPeer hash:long = payments.StarGiftCollections;
|
||||
payments.getUniqueStarGiftValueInfo#4365af6b slug:string = payments.UniqueStarGiftValueInfo;
|
||||
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;
|
||||
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;
|
||||
|
||||
@ -352,6 +352,8 @@
|
||||
"payments.getResaleStarGifts",
|
||||
"payments.updateStarGiftPrice",
|
||||
"payments.getStarGiftCollections",
|
||||
"payments.getStarGiftAuctionState",
|
||||
"payments.getStarGiftAuctionAcquiredGifts",
|
||||
"langpack.getLangPack",
|
||||
"langpack.getStrings",
|
||||
"langpack.getLanguages",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -35,292 +35,295 @@ $icons-map: (
|
||||
"arrow-right": "\f111",
|
||||
"ask-support": "\f112",
|
||||
"attach": "\f113",
|
||||
"auction": "\f114",
|
||||
"author-hidden": "\f115",
|
||||
"avatar-archived-chats": "\f116",
|
||||
"avatar-deleted-account": "\f117",
|
||||
"avatar-saved-messages": "\f118",
|
||||
"bold": "\f119",
|
||||
"boost-outline": "\f11a",
|
||||
"boost": "\f11b",
|
||||
"boostcircle": "\f11c",
|
||||
"boosts": "\f11d",
|
||||
"bot-command": "\f11e",
|
||||
"bot-commands-filled": "\f11f",
|
||||
"bots": "\f120",
|
||||
"bug": "\f121",
|
||||
"calendar-filter": "\f122",
|
||||
"calendar": "\f123",
|
||||
"camera-add": "\f124",
|
||||
"camera": "\f125",
|
||||
"car": "\f126",
|
||||
"card": "\f127",
|
||||
"cash-circle": "\f128",
|
||||
"channel-filled": "\f129",
|
||||
"channel": "\f12a",
|
||||
"channelviews": "\f12b",
|
||||
"chat-badge": "\f12c",
|
||||
"chats-badge": "\f12d",
|
||||
"check": "\f12e",
|
||||
"clock-edit": "\f12f",
|
||||
"clock": "\f130",
|
||||
"close-circle": "\f131",
|
||||
"close-topic": "\f132",
|
||||
"close": "\f133",
|
||||
"closed-gift": "\f134",
|
||||
"cloud-download": "\f135",
|
||||
"collapse-modal": "\f136",
|
||||
"collapse": "\f137",
|
||||
"colorize": "\f138",
|
||||
"comments-sticker": "\f139",
|
||||
"comments": "\f13a",
|
||||
"copy-media": "\f13b",
|
||||
"copy": "\f13c",
|
||||
"crown-take-off-outline": "\f13d",
|
||||
"crown-take-off": "\f13e",
|
||||
"crown-wear-outline": "\f13f",
|
||||
"crown-wear": "\f140",
|
||||
"darkmode": "\f141",
|
||||
"data": "\f142",
|
||||
"delete-filled": "\f143",
|
||||
"delete-left": "\f144",
|
||||
"delete-user": "\f145",
|
||||
"delete": "\f146",
|
||||
"diamond": "\f147",
|
||||
"document": "\f148",
|
||||
"double-badge": "\f149",
|
||||
"down": "\f14a",
|
||||
"download": "\f14b",
|
||||
"dropdown-arrows": "\f14c",
|
||||
"eats": "\f14d",
|
||||
"edit": "\f14e",
|
||||
"email": "\f14f",
|
||||
"enter": "\f150",
|
||||
"expand-modal": "\f151",
|
||||
"expand": "\f152",
|
||||
"eye-crossed-outline": "\f153",
|
||||
"eye-crossed": "\f154",
|
||||
"eye-outline": "\f155",
|
||||
"eye": "\f156",
|
||||
"favorite-filled": "\f157",
|
||||
"favorite": "\f158",
|
||||
"file-badge": "\f159",
|
||||
"flag": "\f15a",
|
||||
"folder-badge": "\f15b",
|
||||
"folder-tabs-bot": "\f15c",
|
||||
"folder-tabs-channel": "\f15d",
|
||||
"folder-tabs-chat": "\f15e",
|
||||
"folder-tabs-chats": "\f15f",
|
||||
"folder-tabs-folder": "\f160",
|
||||
"folder-tabs-group": "\f161",
|
||||
"folder-tabs-star": "\f162",
|
||||
"folder-tabs-user": "\f163",
|
||||
"folder": "\f164",
|
||||
"fontsize": "\f165",
|
||||
"forums": "\f166",
|
||||
"forward": "\f167",
|
||||
"fragment": "\f168",
|
||||
"frozen-time": "\f169",
|
||||
"fullscreen": "\f16a",
|
||||
"gifs": "\f16b",
|
||||
"gift-transfer-inline": "\f16c",
|
||||
"gift": "\f16d",
|
||||
"group-filled": "\f16e",
|
||||
"group": "\f16f",
|
||||
"grouped-disable": "\f170",
|
||||
"grouped": "\f171",
|
||||
"hand-stop": "\f172",
|
||||
"hashtag": "\f173",
|
||||
"hd-photo": "\f174",
|
||||
"heart-outline": "\f175",
|
||||
"heart": "\f176",
|
||||
"help": "\f177",
|
||||
"info-filled": "\f178",
|
||||
"info": "\f179",
|
||||
"install": "\f17a",
|
||||
"italic": "\f17b",
|
||||
"key": "\f17c",
|
||||
"keyboard": "\f17d",
|
||||
"lamp": "\f17e",
|
||||
"language": "\f17f",
|
||||
"large-pause": "\f180",
|
||||
"large-play": "\f181",
|
||||
"link-badge": "\f182",
|
||||
"link-broken": "\f183",
|
||||
"link": "\f184",
|
||||
"location": "\f185",
|
||||
"lock-badge": "\f186",
|
||||
"lock": "\f187",
|
||||
"logout": "\f188",
|
||||
"loop": "\f189",
|
||||
"mention": "\f18a",
|
||||
"menu": "\f18b",
|
||||
"message-failed": "\f18c",
|
||||
"message-pending": "\f18d",
|
||||
"message-read": "\f18e",
|
||||
"message-succeeded": "\f18f",
|
||||
"message": "\f190",
|
||||
"microphone-alt": "\f191",
|
||||
"microphone": "\f192",
|
||||
"monospace": "\f193",
|
||||
"more-circle": "\f194",
|
||||
"more": "\f195",
|
||||
"move-caption-down": "\f196",
|
||||
"move-caption-up": "\f197",
|
||||
"mute": "\f198",
|
||||
"muted": "\f199",
|
||||
"my-notes": "\f19a",
|
||||
"new-chat-filled": "\f19b",
|
||||
"next": "\f19c",
|
||||
"nochannel": "\f19d",
|
||||
"noise-suppression": "\f19e",
|
||||
"non-contacts": "\f19f",
|
||||
"note": "\f1a0",
|
||||
"one-filled": "\f1a1",
|
||||
"open-in-new-tab": "\f1a2",
|
||||
"password-off": "\f1a3",
|
||||
"pause": "\f1a4",
|
||||
"permissions": "\f1a5",
|
||||
"phone-discard-outline": "\f1a6",
|
||||
"phone-discard": "\f1a7",
|
||||
"phone": "\f1a8",
|
||||
"photo": "\f1a9",
|
||||
"pin-badge": "\f1aa",
|
||||
"pin-list": "\f1ab",
|
||||
"pin": "\f1ac",
|
||||
"pinned-chat": "\f1ad",
|
||||
"pinned-message": "\f1ae",
|
||||
"pip": "\f1af",
|
||||
"play-story": "\f1b0",
|
||||
"play": "\f1b1",
|
||||
"poll": "\f1b2",
|
||||
"previous": "\f1b3",
|
||||
"privacy-policy": "\f1b4",
|
||||
"proof-of-ownership": "\f1b5",
|
||||
"quote-text": "\f1b6",
|
||||
"quote": "\f1b7",
|
||||
"radial-badge": "\f1b8",
|
||||
"rating-icons-level1": "\f1b9",
|
||||
"rating-icons-level10": "\f1ba",
|
||||
"rating-icons-level2": "\f1bb",
|
||||
"rating-icons-level20": "\f1bc",
|
||||
"rating-icons-level3": "\f1bd",
|
||||
"rating-icons-level30": "\f1be",
|
||||
"rating-icons-level4": "\f1bf",
|
||||
"rating-icons-level40": "\f1c0",
|
||||
"rating-icons-level5": "\f1c1",
|
||||
"rating-icons-level50": "\f1c2",
|
||||
"rating-icons-level6": "\f1c3",
|
||||
"rating-icons-level60": "\f1c4",
|
||||
"rating-icons-level7": "\f1c5",
|
||||
"rating-icons-level70": "\f1c6",
|
||||
"rating-icons-level8": "\f1c7",
|
||||
"rating-icons-level80": "\f1c8",
|
||||
"rating-icons-level9": "\f1c9",
|
||||
"rating-icons-level90": "\f1ca",
|
||||
"rating-icons-negative": "\f1cb",
|
||||
"readchats": "\f1cc",
|
||||
"recent": "\f1cd",
|
||||
"refund": "\f1ce",
|
||||
"reload": "\f1cf",
|
||||
"remove-quote": "\f1d0",
|
||||
"remove": "\f1d1",
|
||||
"reopen-topic": "\f1d2",
|
||||
"reorder-tabs": "\f1d3",
|
||||
"replace": "\f1d4",
|
||||
"replies": "\f1d5",
|
||||
"reply-filled": "\f1d6",
|
||||
"reply": "\f1d7",
|
||||
"revenue-split": "\f1d8",
|
||||
"revote": "\f1d9",
|
||||
"save-story": "\f1da",
|
||||
"saved-messages": "\f1db",
|
||||
"schedule": "\f1dc",
|
||||
"scheduled": "\f1dd",
|
||||
"sd-photo": "\f1de",
|
||||
"search": "\f1df",
|
||||
"select": "\f1e0",
|
||||
"sell-outline": "\f1e1",
|
||||
"sell": "\f1e2",
|
||||
"send-outline": "\f1e3",
|
||||
"send": "\f1e4",
|
||||
"settings-filled": "\f1e5",
|
||||
"settings": "\f1e6",
|
||||
"share-filled": "\f1e7",
|
||||
"share-screen-outlined": "\f1e8",
|
||||
"share-screen-stop": "\f1e9",
|
||||
"share-screen": "\f1ea",
|
||||
"show-message": "\f1eb",
|
||||
"sidebar": "\f1ec",
|
||||
"skip-next": "\f1ed",
|
||||
"skip-previous": "\f1ee",
|
||||
"smallscreen": "\f1ef",
|
||||
"smile": "\f1f0",
|
||||
"sort-by-date": "\f1f1",
|
||||
"sort-by-number": "\f1f2",
|
||||
"sort-by-price": "\f1f3",
|
||||
"sort": "\f1f4",
|
||||
"speaker-muted-story": "\f1f5",
|
||||
"speaker-outline": "\f1f6",
|
||||
"speaker-story": "\f1f7",
|
||||
"speaker": "\f1f8",
|
||||
"spoiler-disable": "\f1f9",
|
||||
"spoiler": "\f1fa",
|
||||
"sport": "\f1fb",
|
||||
"star": "\f1fc",
|
||||
"stars-lock": "\f1fd",
|
||||
"stars-refund": "\f1fe",
|
||||
"stats": "\f1ff",
|
||||
"stealth-future": "\f200",
|
||||
"stealth-past": "\f201",
|
||||
"stickers": "\f202",
|
||||
"stop-raising-hand": "\f203",
|
||||
"stop": "\f204",
|
||||
"story-caption": "\f205",
|
||||
"story-expired": "\f206",
|
||||
"story-priority": "\f207",
|
||||
"story-reply": "\f208",
|
||||
"strikethrough": "\f209",
|
||||
"tag-add": "\f20a",
|
||||
"tag-crossed": "\f20b",
|
||||
"tag-filter": "\f20c",
|
||||
"tag-name": "\f20d",
|
||||
"tag": "\f20e",
|
||||
"timer": "\f20f",
|
||||
"toncoin": "\f210",
|
||||
"tools": "\f211",
|
||||
"topic-new": "\f212",
|
||||
"trade": "\f213",
|
||||
"transcribe": "\f214",
|
||||
"truck": "\f215",
|
||||
"unarchive": "\f216",
|
||||
"underlined": "\f217",
|
||||
"understood": "\f218",
|
||||
"unique-profile": "\f219",
|
||||
"unlist-outline": "\f21a",
|
||||
"unlist": "\f21b",
|
||||
"unlock-badge": "\f21c",
|
||||
"unlock": "\f21d",
|
||||
"unmute": "\f21e",
|
||||
"unpin": "\f21f",
|
||||
"unread": "\f220",
|
||||
"up": "\f221",
|
||||
"user-filled": "\f222",
|
||||
"user-online": "\f223",
|
||||
"user-stars": "\f224",
|
||||
"user": "\f225",
|
||||
"video-outlined": "\f226",
|
||||
"video-stop": "\f227",
|
||||
"video": "\f228",
|
||||
"view-once": "\f229",
|
||||
"voice-chat": "\f22a",
|
||||
"volume-1": "\f22b",
|
||||
"volume-2": "\f22c",
|
||||
"volume-3": "\f22d",
|
||||
"warning": "\f22e",
|
||||
"web": "\f22f",
|
||||
"webapp": "\f230",
|
||||
"word-wrap": "\f231",
|
||||
"zoom-in": "\f232",
|
||||
"zoom-out": "\f233",
|
||||
"auction-drop": "\f114",
|
||||
"auction-filled": "\f115",
|
||||
"auction-next-round": "\f116",
|
||||
"auction": "\f117",
|
||||
"author-hidden": "\f118",
|
||||
"avatar-archived-chats": "\f119",
|
||||
"avatar-deleted-account": "\f11a",
|
||||
"avatar-saved-messages": "\f11b",
|
||||
"bold": "\f11c",
|
||||
"boost-outline": "\f11d",
|
||||
"boost": "\f11e",
|
||||
"boostcircle": "\f11f",
|
||||
"boosts": "\f120",
|
||||
"bot-command": "\f121",
|
||||
"bot-commands-filled": "\f122",
|
||||
"bots": "\f123",
|
||||
"bug": "\f124",
|
||||
"calendar-filter": "\f125",
|
||||
"calendar": "\f126",
|
||||
"camera-add": "\f127",
|
||||
"camera": "\f128",
|
||||
"car": "\f129",
|
||||
"card": "\f12a",
|
||||
"cash-circle": "\f12b",
|
||||
"channel-filled": "\f12c",
|
||||
"channel": "\f12d",
|
||||
"channelviews": "\f12e",
|
||||
"chat-badge": "\f12f",
|
||||
"chats-badge": "\f130",
|
||||
"check": "\f131",
|
||||
"clock-edit": "\f132",
|
||||
"clock": "\f133",
|
||||
"close-circle": "\f134",
|
||||
"close-topic": "\f135",
|
||||
"close": "\f136",
|
||||
"closed-gift": "\f137",
|
||||
"cloud-download": "\f138",
|
||||
"collapse-modal": "\f139",
|
||||
"collapse": "\f13a",
|
||||
"colorize": "\f13b",
|
||||
"comments-sticker": "\f13c",
|
||||
"comments": "\f13d",
|
||||
"copy-media": "\f13e",
|
||||
"copy": "\f13f",
|
||||
"crown-take-off-outline": "\f140",
|
||||
"crown-take-off": "\f141",
|
||||
"crown-wear-outline": "\f142",
|
||||
"crown-wear": "\f143",
|
||||
"darkmode": "\f144",
|
||||
"data": "\f145",
|
||||
"delete-filled": "\f146",
|
||||
"delete-left": "\f147",
|
||||
"delete-user": "\f148",
|
||||
"delete": "\f149",
|
||||
"diamond": "\f14a",
|
||||
"document": "\f14b",
|
||||
"double-badge": "\f14c",
|
||||
"down": "\f14d",
|
||||
"download": "\f14e",
|
||||
"dropdown-arrows": "\f14f",
|
||||
"eats": "\f150",
|
||||
"edit": "\f151",
|
||||
"email": "\f152",
|
||||
"enter": "\f153",
|
||||
"expand-modal": "\f154",
|
||||
"expand": "\f155",
|
||||
"eye-crossed-outline": "\f156",
|
||||
"eye-crossed": "\f157",
|
||||
"eye-outline": "\f158",
|
||||
"eye": "\f159",
|
||||
"favorite-filled": "\f15a",
|
||||
"favorite": "\f15b",
|
||||
"file-badge": "\f15c",
|
||||
"flag": "\f15d",
|
||||
"folder-badge": "\f15e",
|
||||
"folder-tabs-bot": "\f15f",
|
||||
"folder-tabs-channel": "\f160",
|
||||
"folder-tabs-chat": "\f161",
|
||||
"folder-tabs-chats": "\f162",
|
||||
"folder-tabs-folder": "\f163",
|
||||
"folder-tabs-group": "\f164",
|
||||
"folder-tabs-star": "\f165",
|
||||
"folder-tabs-user": "\f166",
|
||||
"folder": "\f167",
|
||||
"fontsize": "\f168",
|
||||
"forums": "\f169",
|
||||
"forward": "\f16a",
|
||||
"fragment": "\f16b",
|
||||
"frozen-time": "\f16c",
|
||||
"fullscreen": "\f16d",
|
||||
"gifs": "\f16e",
|
||||
"gift-transfer-inline": "\f16f",
|
||||
"gift": "\f170",
|
||||
"group-filled": "\f171",
|
||||
"group": "\f172",
|
||||
"grouped-disable": "\f173",
|
||||
"grouped": "\f174",
|
||||
"hand-stop": "\f175",
|
||||
"hashtag": "\f176",
|
||||
"hd-photo": "\f177",
|
||||
"heart-outline": "\f178",
|
||||
"heart": "\f179",
|
||||
"help": "\f17a",
|
||||
"info-filled": "\f17b",
|
||||
"info": "\f17c",
|
||||
"install": "\f17d",
|
||||
"italic": "\f17e",
|
||||
"key": "\f17f",
|
||||
"keyboard": "\f180",
|
||||
"lamp": "\f181",
|
||||
"language": "\f182",
|
||||
"large-pause": "\f183",
|
||||
"large-play": "\f184",
|
||||
"link-badge": "\f185",
|
||||
"link-broken": "\f186",
|
||||
"link": "\f187",
|
||||
"location": "\f188",
|
||||
"lock-badge": "\f189",
|
||||
"lock": "\f18a",
|
||||
"logout": "\f18b",
|
||||
"loop": "\f18c",
|
||||
"mention": "\f18d",
|
||||
"menu": "\f18e",
|
||||
"message-failed": "\f18f",
|
||||
"message-pending": "\f190",
|
||||
"message-read": "\f191",
|
||||
"message-succeeded": "\f192",
|
||||
"message": "\f193",
|
||||
"microphone-alt": "\f194",
|
||||
"microphone": "\f195",
|
||||
"monospace": "\f196",
|
||||
"more-circle": "\f197",
|
||||
"more": "\f198",
|
||||
"move-caption-down": "\f199",
|
||||
"move-caption-up": "\f19a",
|
||||
"mute": "\f19b",
|
||||
"muted": "\f19c",
|
||||
"my-notes": "\f19d",
|
||||
"new-chat-filled": "\f19e",
|
||||
"next": "\f19f",
|
||||
"nochannel": "\f1a0",
|
||||
"noise-suppression": "\f1a1",
|
||||
"non-contacts": "\f1a2",
|
||||
"note": "\f1a3",
|
||||
"one-filled": "\f1a4",
|
||||
"open-in-new-tab": "\f1a5",
|
||||
"password-off": "\f1a6",
|
||||
"pause": "\f1a7",
|
||||
"permissions": "\f1a8",
|
||||
"phone-discard-outline": "\f1a9",
|
||||
"phone-discard": "\f1aa",
|
||||
"phone": "\f1ab",
|
||||
"photo": "\f1ac",
|
||||
"pin-badge": "\f1ad",
|
||||
"pin-list": "\f1ae",
|
||||
"pin": "\f1af",
|
||||
"pinned-chat": "\f1b0",
|
||||
"pinned-message": "\f1b1",
|
||||
"pip": "\f1b2",
|
||||
"play-story": "\f1b3",
|
||||
"play": "\f1b4",
|
||||
"poll": "\f1b5",
|
||||
"previous": "\f1b6",
|
||||
"privacy-policy": "\f1b7",
|
||||
"proof-of-ownership": "\f1b8",
|
||||
"quote-text": "\f1b9",
|
||||
"quote": "\f1ba",
|
||||
"radial-badge": "\f1bb",
|
||||
"rating-icons-level1": "\f1bc",
|
||||
"rating-icons-level10": "\f1bd",
|
||||
"rating-icons-level2": "\f1be",
|
||||
"rating-icons-level20": "\f1bf",
|
||||
"rating-icons-level3": "\f1c0",
|
||||
"rating-icons-level30": "\f1c1",
|
||||
"rating-icons-level4": "\f1c2",
|
||||
"rating-icons-level40": "\f1c3",
|
||||
"rating-icons-level5": "\f1c4",
|
||||
"rating-icons-level50": "\f1c5",
|
||||
"rating-icons-level6": "\f1c6",
|
||||
"rating-icons-level60": "\f1c7",
|
||||
"rating-icons-level7": "\f1c8",
|
||||
"rating-icons-level70": "\f1c9",
|
||||
"rating-icons-level8": "\f1ca",
|
||||
"rating-icons-level80": "\f1cb",
|
||||
"rating-icons-level9": "\f1cc",
|
||||
"rating-icons-level90": "\f1cd",
|
||||
"rating-icons-negative": "\f1ce",
|
||||
"readchats": "\f1cf",
|
||||
"recent": "\f1d0",
|
||||
"refund": "\f1d1",
|
||||
"reload": "\f1d2",
|
||||
"remove-quote": "\f1d3",
|
||||
"remove": "\f1d4",
|
||||
"reopen-topic": "\f1d5",
|
||||
"reorder-tabs": "\f1d6",
|
||||
"replace": "\f1d7",
|
||||
"replies": "\f1d8",
|
||||
"reply-filled": "\f1d9",
|
||||
"reply": "\f1da",
|
||||
"revenue-split": "\f1db",
|
||||
"revote": "\f1dc",
|
||||
"save-story": "\f1dd",
|
||||
"saved-messages": "\f1de",
|
||||
"schedule": "\f1df",
|
||||
"scheduled": "\f1e0",
|
||||
"sd-photo": "\f1e1",
|
||||
"search": "\f1e2",
|
||||
"select": "\f1e3",
|
||||
"sell-outline": "\f1e4",
|
||||
"sell": "\f1e5",
|
||||
"send-outline": "\f1e6",
|
||||
"send": "\f1e7",
|
||||
"settings-filled": "\f1e8",
|
||||
"settings": "\f1e9",
|
||||
"share-filled": "\f1ea",
|
||||
"share-screen-outlined": "\f1eb",
|
||||
"share-screen-stop": "\f1ec",
|
||||
"share-screen": "\f1ed",
|
||||
"show-message": "\f1ee",
|
||||
"sidebar": "\f1ef",
|
||||
"skip-next": "\f1f0",
|
||||
"skip-previous": "\f1f1",
|
||||
"smallscreen": "\f1f2",
|
||||
"smile": "\f1f3",
|
||||
"sort-by-date": "\f1f4",
|
||||
"sort-by-number": "\f1f5",
|
||||
"sort-by-price": "\f1f6",
|
||||
"sort": "\f1f7",
|
||||
"speaker-muted-story": "\f1f8",
|
||||
"speaker-outline": "\f1f9",
|
||||
"speaker-story": "\f1fa",
|
||||
"speaker": "\f1fb",
|
||||
"spoiler-disable": "\f1fc",
|
||||
"spoiler": "\f1fd",
|
||||
"sport": "\f1fe",
|
||||
"star": "\f1ff",
|
||||
"stars-lock": "\f200",
|
||||
"stars-refund": "\f201",
|
||||
"stats": "\f202",
|
||||
"stealth-future": "\f203",
|
||||
"stealth-past": "\f204",
|
||||
"stickers": "\f205",
|
||||
"stop-raising-hand": "\f206",
|
||||
"stop": "\f207",
|
||||
"story-caption": "\f208",
|
||||
"story-expired": "\f209",
|
||||
"story-priority": "\f20a",
|
||||
"story-reply": "\f20b",
|
||||
"strikethrough": "\f20c",
|
||||
"tag-add": "\f20d",
|
||||
"tag-crossed": "\f20e",
|
||||
"tag-filter": "\f20f",
|
||||
"tag-name": "\f210",
|
||||
"tag": "\f211",
|
||||
"timer": "\f212",
|
||||
"toncoin": "\f213",
|
||||
"tools": "\f214",
|
||||
"topic-new": "\f215",
|
||||
"trade": "\f216",
|
||||
"transcribe": "\f217",
|
||||
"truck": "\f218",
|
||||
"unarchive": "\f219",
|
||||
"underlined": "\f21a",
|
||||
"understood": "\f21b",
|
||||
"unique-profile": "\f21c",
|
||||
"unlist-outline": "\f21d",
|
||||
"unlist": "\f21e",
|
||||
"unlock-badge": "\f21f",
|
||||
"unlock": "\f220",
|
||||
"unmute": "\f221",
|
||||
"unpin": "\f222",
|
||||
"unread": "\f223",
|
||||
"up": "\f224",
|
||||
"user-filled": "\f225",
|
||||
"user-online": "\f226",
|
||||
"user-stars": "\f227",
|
||||
"user": "\f228",
|
||||
"video-outlined": "\f229",
|
||||
"video-stop": "\f22a",
|
||||
"video": "\f22b",
|
||||
"view-once": "\f22c",
|
||||
"voice-chat": "\f22d",
|
||||
"volume-1": "\f22e",
|
||||
"volume-2": "\f22f",
|
||||
"volume-3": "\f230",
|
||||
"warning": "\f231",
|
||||
"web": "\f232",
|
||||
"webapp": "\f233",
|
||||
"word-wrap": "\f234",
|
||||
"zoom-in": "\f235",
|
||||
"zoom-out": "\f236",
|
||||
);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -18,6 +18,9 @@ export type FontIconName =
|
||||
| 'arrow-right'
|
||||
| 'ask-support'
|
||||
| 'attach'
|
||||
| 'auction-drop'
|
||||
| 'auction-filled'
|
||||
| 'auction-next-round'
|
||||
| 'auction'
|
||||
| 'author-hidden'
|
||||
| 'avatar-archived-chats'
|
||||
|
||||
110
src/types/language.d.ts
vendored
110
src/types/language.d.ts
vendored
@ -1268,6 +1268,7 @@ export interface LangPair {
|
||||
'GiftInfoDescriptionFreeUpgrade': undefined;
|
||||
'GiftInfoDescriptionUpgrade2': undefined;
|
||||
'GiftInfoDescriptionUpgraded': undefined;
|
||||
'GiftInfoDescriptionRefunded': undefined;
|
||||
'GiftInfoFrom': undefined;
|
||||
'GiftInfoDate': undefined;
|
||||
'GiftInfoValue': undefined;
|
||||
@ -1745,6 +1746,8 @@ export interface LangPair {
|
||||
'StarGiftReasonDropOriginalDetails': undefined;
|
||||
'GiftAnUpgradeButton': undefined;
|
||||
'GiftPrepaidUpgradeTransactionTitle': undefined;
|
||||
'StarGiftAuctionBidTransaction': undefined;
|
||||
'StarGiftAuctionBidRefundedTransaction': undefined;
|
||||
'ActionStarGiftPrepaidUpgradedYou': undefined;
|
||||
'UserNoteTitle': undefined;
|
||||
'UserNoteHint': undefined;
|
||||
@ -1787,6 +1790,39 @@ export interface LangPair {
|
||||
'StarGiftPriceDecreaseInfoLink': undefined;
|
||||
'StarGiftUpgradeCostModalTitle': undefined;
|
||||
'StarGiftUpgradeCostHint': undefined;
|
||||
'GiftRibbonAuction': undefined;
|
||||
'GiftAuctionJoin': undefined;
|
||||
'GiftAuctionLearnMore': undefined;
|
||||
'GiftAuctionStarted': undefined;
|
||||
'GiftAuctionEnds': undefined;
|
||||
'GiftAuctionCurrentRound': undefined;
|
||||
'GiftAuctionPlaceBid': undefined;
|
||||
'GiftAuctionMinimumBid': undefined;
|
||||
'GiftAuctionUntilNextRound': undefined;
|
||||
'GiftAuctionLeft': undefined;
|
||||
'GiftAuctionYourBidWillBe': undefined;
|
||||
'GiftAuctionYoureWinning': undefined;
|
||||
'GiftAuctionBalance': undefined;
|
||||
'GiftAuctionInfoTitle': undefined;
|
||||
'GiftAuctionInfoSubtitle': undefined;
|
||||
'GiftAuctionInfoBidCarryoverTitle': undefined;
|
||||
'GiftAuctionInfoMissedBiddersTitle': undefined;
|
||||
'GiftAuctionInfoMissedBiddersSubtitle': undefined;
|
||||
'GiftAuctionRecipient': undefined;
|
||||
'GiftAuctionDate': undefined;
|
||||
'GiftAuctionAcceptedBid': undefined;
|
||||
'GiftAuctionCustomBidTitle': undefined;
|
||||
'GiftAuctionCustomBidPlaceholder': undefined;
|
||||
'GiftAuctionCustomBidButton': undefined;
|
||||
'GiftAuctionBidPlacedTitle': undefined;
|
||||
'GiftAuctionBidIncreasedTitle': undefined;
|
||||
'GiftAuctionFinished': undefined;
|
||||
'GiftAuctionEnded': undefined;
|
||||
'GiftAuctionSoldOut': undefined;
|
||||
'GiftAuctionChangeRecipientTitle': undefined;
|
||||
'GiftAuctionAveragePrice': undefined;
|
||||
'GiftAuctionTapToBidMore': undefined;
|
||||
'StarGift': undefined;
|
||||
'SettingsItemPrivacyPasskeys': undefined;
|
||||
'SettingsItemPrivacyOn': undefined;
|
||||
'SettingsItemPrivacyOff': undefined;
|
||||
@ -2645,6 +2681,15 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'ActionStarGiftLimitedRibbon': {
|
||||
'total': V;
|
||||
};
|
||||
'ActionStarGiftAuctionWon': {
|
||||
'cost': V;
|
||||
};
|
||||
'ActionStarGiftAuctionFor': {
|
||||
'peer': V;
|
||||
};
|
||||
'ActionStarGiftAuctionBought': {
|
||||
'cost': V;
|
||||
};
|
||||
'ActionSuggestedPhotoYou': {
|
||||
'user': V;
|
||||
};
|
||||
@ -3120,6 +3165,43 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'StarGiftPriceDecreaseTimer': {
|
||||
'timer': V;
|
||||
};
|
||||
'GiftAuctionRoundValue': {
|
||||
'current': V;
|
||||
'total': V;
|
||||
};
|
||||
'GiftAuctionPlaceBidButton': {
|
||||
'amount': V;
|
||||
};
|
||||
'GiftAuctionTimeLeft': {
|
||||
'time': V;
|
||||
};
|
||||
'GiftAuctionAddToBid': {
|
||||
'amount': V;
|
||||
};
|
||||
'GiftAuctionInfoBidCarryoverSubtitle': {
|
||||
'count': V;
|
||||
};
|
||||
'GiftAuctionBoughtGiftHeader': {
|
||||
'gift': V;
|
||||
'giftNumber': V;
|
||||
'round': V;
|
||||
};
|
||||
'GiftAuctionTopPosition': {
|
||||
'position': V;
|
||||
};
|
||||
'GiftAuctionCustomBidDescription': {
|
||||
'count': V;
|
||||
};
|
||||
'GiftAuctionBidPlacedMessage': {
|
||||
'count': V;
|
||||
};
|
||||
'GiftAuctionChangeRecipientDescription': {
|
||||
'oldPeer': V;
|
||||
'newPeer': V;
|
||||
};
|
||||
'GiftAuctionWonNotification': {
|
||||
'gift': V;
|
||||
};
|
||||
'SettingsPasskeyUsedAt': {
|
||||
'date': V;
|
||||
};
|
||||
@ -3527,6 +3609,34 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
|
||||
'list': V;
|
||||
'count': V;
|
||||
};
|
||||
'GiftAuctionTopBidders': {
|
||||
'count': V;
|
||||
'gift': V;
|
||||
'link': V;
|
||||
};
|
||||
'GiftAuctionDescription': {
|
||||
'count': V;
|
||||
'link': V;
|
||||
};
|
||||
'GiftAuctionTopWinners': {
|
||||
'count': V;
|
||||
};
|
||||
'GiftAuctionInfoTopBiddersTitle': {
|
||||
'count': V;
|
||||
};
|
||||
'GiftAuctionInfoTopBiddersSubtitle': {
|
||||
'count': V;
|
||||
};
|
||||
'GiftAuctionItemsBought': {
|
||||
'count': V;
|
||||
'gift': V;
|
||||
};
|
||||
'GiftAuctionBoughtGiftsTitle': {
|
||||
'count': V;
|
||||
};
|
||||
'GiftAuctionGifts': {
|
||||
'count': V;
|
||||
};
|
||||
}
|
||||
export type RegularLangKey = keyof LangPair;
|
||||
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
||||
|
||||
@ -10,7 +10,7 @@ import { isUsernameValid } from './entities/username';
|
||||
export type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'addemoji' |
|
||||
'setlanguage' | 'addtheme' | 'confirmphone' | 'socks' | 'proxy' | 'privatepost' | 'bg' | 'share' | 'msg' | 'msg_url' |
|
||||
'invoice' | 'addlist' | 'boost' | 'giftcode' | 'message' | 'premium_offer' | 'premium_multigift' | 'stars_topup'
|
||||
| 'nft' | 'stars' | 'ton';
|
||||
| 'nft' | 'stars' | 'ton' | 'stargift_auction';
|
||||
|
||||
interface PublicMessageLink {
|
||||
type: 'publicMessageLink';
|
||||
@ -104,6 +104,11 @@ interface GiftUniqueLink {
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface GiftAuctionLink {
|
||||
type: 'giftAuctionLink';
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface StarsModalLink {
|
||||
type: 'stars';
|
||||
}
|
||||
@ -131,6 +136,7 @@ type DeepLink =
|
||||
PremiumMultigiftLink |
|
||||
ChatBoostLink |
|
||||
GiftUniqueLink |
|
||||
GiftAuctionLink |
|
||||
StarsModalLink |
|
||||
TonModalLink |
|
||||
SettingsScreenLink;
|
||||
@ -269,6 +275,8 @@ function parseTgLink(url: URL) {
|
||||
return buildChatBoostLink({ username: queryParams.domain, id: queryParams.channel });
|
||||
case 'giftUniqueLink':
|
||||
return buildGiftUniqueLink({ slug: queryParams.slug });
|
||||
case 'giftAuctionLink':
|
||||
return buildGiftAuctionLink({ slug: queryParams.slug });
|
||||
case 'stars':
|
||||
return { type: 'stars' } satisfies StarsModalLink;
|
||||
case 'ton':
|
||||
@ -384,6 +392,11 @@ function parseHttpLink(url: URL) {
|
||||
slug,
|
||||
});
|
||||
}
|
||||
case 'giftAuctionLink': {
|
||||
return buildGiftAuctionLink({
|
||||
slug: pathParams[1],
|
||||
});
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -409,6 +422,7 @@ function getHttpDeepLinkType(
|
||||
if (method === 'm') return 'businessChatLink';
|
||||
if (method === 'boost') return 'chatBoostLink';
|
||||
if (method === 'nft') return 'giftUniqueLink';
|
||||
if (method === 'auction') return 'giftAuctionLink';
|
||||
if (method === 'c') {
|
||||
if (queryParams.boost !== undefined) return 'chatBoostLink';
|
||||
return 'privateChannelLink';
|
||||
@ -481,6 +495,8 @@ function getTgDeepLinkType(
|
||||
return 'chatBoostLink';
|
||||
case 'nft':
|
||||
return 'giftUniqueLink';
|
||||
case 'stargift_auction':
|
||||
return 'giftAuctionLink';
|
||||
case 'stars':
|
||||
return 'stars';
|
||||
case 'ton':
|
||||
@ -710,6 +726,21 @@ function buildGiftUniqueLink(params: BuilderParams<GiftUniqueLink>): BuilderRetu
|
||||
};
|
||||
}
|
||||
|
||||
function buildGiftAuctionLink(params: BuilderParams<GiftAuctionLink>): BuilderReturnType<GiftAuctionLink> {
|
||||
const {
|
||||
slug,
|
||||
} = params;
|
||||
|
||||
if (!slug) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'giftAuctionLink',
|
||||
slug,
|
||||
};
|
||||
}
|
||||
|
||||
function buildSettingsScreen(screenParam: string) {
|
||||
switch (screenParam) {
|
||||
case 'devices':
|
||||
|
||||
@ -79,6 +79,9 @@ export const processDeepLink = (url: string, linkContext?: LinkContext): boolean
|
||||
case 'giftUniqueLink':
|
||||
actions.openUniqueGiftBySlug({ slug: parsedLink.slug });
|
||||
return true;
|
||||
case 'giftAuctionLink':
|
||||
actions.openGiftAuctionBySlug({ slug: parsedLink.slug });
|
||||
return true;
|
||||
case 'settings':
|
||||
if (!parsedLink.screen) {
|
||||
actions.openLeftColumnContent({ contentKey: LeftColumnContent.Settings });
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user