Chat List: Show active auctions (#6641)

This commit is contained in:
zubiden 2026-02-22 23:42:54 +01:00 committed by Alexander Zinchuk
parent 31eaf0e5dd
commit d0ba0c2530
35 changed files with 580 additions and 80 deletions

View File

@ -442,7 +442,7 @@ export function buildApiStarGiftAuctionUserState(
} }
export function buildApiStarGiftAuctionState( export function buildApiStarGiftAuctionState(
result: GramJs.payments.StarGiftAuctionState, result: GramJs.payments.StarGiftAuctionState | GramJs.StarGiftActiveAuctionState,
): ApiStarGiftAuctionState | undefined { ): ApiStarGiftAuctionState | undefined {
const gift = buildApiStarGift(result.gift); const gift = buildApiStarGift(result.gift);
if (gift.type !== 'starGift') return undefined; if (gift.type !== 'starGift') return undefined;
@ -454,7 +454,7 @@ export function buildApiStarGiftAuctionState(
gift, gift,
state, state,
userState: buildApiStarGiftAuctionUserState(result.userState), userState: buildApiStarGiftAuctionUserState(result.userState),
timeout: result.timeout, timeout: 'timeout' in result ? result.timeout : undefined,
}; };
} }

View File

@ -459,6 +459,20 @@ export async function fetchStarGiftAuctionAcquiredGifts({
}; };
} }
export async function fetchStarGiftActiveAuctions() {
const result = await invokeRequest(new GramJs.payments.GetStarGiftActiveAuctions({
hash: DEFAULT_PRIMITIVES.BIGINT,
}));
if (!result || result instanceof GramJs.payments.StarGiftActiveAuctionsNotModified) {
return undefined;
}
return {
auctions: result.auctions.map(buildApiStarGiftAuctionState).filter(Boolean),
};
}
export function upgradeStarGift({ export function upgradeStarGift({
inputSavedGift, inputSavedGift,
shouldKeepOriginalDetails, shouldKeepOriginalDetails,

View File

@ -387,7 +387,7 @@ export interface ApiStarGiftAuctionState {
gift: ApiStarGiftRegular; gift: ApiStarGiftRegular;
state: ApiTypeStarGiftAuctionState; state: ApiTypeStarGiftAuctionState;
userState: ApiStarGiftAuctionUserState; userState: ApiStarGiftAuctionUserState;
timeout: number; timeout?: number;
} }
export interface ApiStarGiftAuctionAcquiredGift { export interface ApiStarGiftAuctionAcquiredGift {

View File

@ -2508,6 +2508,11 @@
"GiftAuctionWonNotification" = "You won {gift} at the auction!"; "GiftAuctionWonNotification" = "You won {gift} at the auction!";
"GiftAuctionLearnMoreAboutGifts" = "Learn more about Telegram Gifts >"; "GiftAuctionLearnMoreAboutGifts" = "Learn more about Telegram Gifts >";
"GiftAuctionLearnMoreMenuItem" = "Learn More"; "GiftAuctionLearnMoreMenuItem" = "Learn More";
"GiftAuctionBidPosition" = "Your bid of {amount} is ranked #{position}";
"GiftAuctionListRaiseBid" = "Raise";
"GiftAuctionListRound" = "Round {current} of {total}";
"GiftAuctionActiveTitle" = "Active Auctions";
"GiftAuctionNoActive" = "You are not participating in any auctions";
"StarGiftInfoTitle" = "Telegram Gifts"; "StarGiftInfoTitle" = "Telegram Gifts";
"StarGiftInfoSubtitle" = "Gifts are collectible items you can trade or showcase on your profile."; "StarGiftInfoSubtitle" = "Gifts are collectible items you can trade or showcase on your profile.";
"StarGiftInfoUniqueTitle" = "Unique"; "StarGiftInfoUniqueTitle" = "Unique";
@ -2603,3 +2608,9 @@
"SettingsDataClearMediaCache" = "Clear Media Cache"; "SettingsDataClearMediaCache" = "Clear Media Cache";
"SettingsDataClearMediaCacheDescription" = "Deletes locally cached media for this account"; "SettingsDataClearMediaCacheDescription" = "Deletes locally cached media for this account";
"SettingsDataClearMediaDone" = "Media cache cleared"; "SettingsDataClearMediaDone" = "Media cache cleared";
"ChatListAuctionTitle_one" = "Active Auction";
"ChatListAuctionTitle_other" = "{count} Active Auctions";
"ChatListAuctionWinning" = "You are winning";
"ChatListAuctionOutbid" = "You've been outbid";
"ChatListAuctionMixed" = "You are winning in {winCount} and outbid in {outbidCount}";
"ChatListAuctionView" = "View";

View File

@ -1,5 +1,4 @@
/* eslint-disable @stylistic/max-len */ /* eslint-disable @stylistic/max-len */
export { default as StarsGiftModal } from '../components/modals/stars/gift/StarsGiftModal'; export { default as StarsGiftModal } from '../components/modals/stars/gift/StarsGiftModal';
export { default as StarsGiftingPickerModal } from '../components/main/premium/StarsGiftingPickerModal'; export { default as StarsGiftingPickerModal } from '../components/main/premium/StarsGiftingPickerModal';
export { default as StarsBalanceModal } from '../components/modals/stars/StarsBalanceModal'; export { default as StarsBalanceModal } from '../components/modals/stars/StarsBalanceModal';
@ -29,3 +28,4 @@ export { default as GiftDescriptionRemoveModal } from '../components/modals/gift
export { default as GiftOfferAcceptModal } from '../components/modals/gift/offer/GiftOfferAcceptModal'; export { default as GiftOfferAcceptModal } from '../components/modals/gift/offer/GiftOfferAcceptModal';
export { default as ChatRefundModal } from '../components/modals/stars/chatRefund/ChatRefundModal'; export { default as ChatRefundModal } from '../components/modals/stars/chatRefund/ChatRefundModal';
export { default as PriceConfirmModal } from '../components/modals/priceConfirm/PriceConfirmModal'; export { default as PriceConfirmModal } from '../components/modals/priceConfirm/PriceConfirmModal';
export { default as ActiveGiftAuctionsModal } from '../components/modals/gift/auction/ActiveGiftAuctionsModal';

View File

@ -1,9 +1,9 @@
import type { ElementRef, FC } from '../../lib/teact/teact'; import type { ElementRef } from '../../lib/teact/teact';
import { memo, useRef, useState } from '../../lib/teact/teact'; import { memo, useRef, useState } from '../../lib/teact/teact';
import { getGlobal } from '../../global'; import { getGlobal } from '../../global';
import type { ObserveFn } from '../../hooks/useIntersectionObserver'; import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import { ApiMessageEntityTypes } from '../../api/types'; import { ApiMessageEntityTypes, type ApiSticker } from '../../api/types';
import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors'; import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
@ -21,7 +21,6 @@ import blankImg from '../../assets/blank.png';
type OwnProps = { type OwnProps = {
ref?: ElementRef<HTMLDivElement>; ref?: ElementRef<HTMLDivElement>;
documentId: string;
className?: string; className?: string;
style?: string; style?: string;
size?: number; size?: number;
@ -42,13 +41,20 @@ type OwnProps = {
observeIntersectionForPlaying?: ObserveFn; observeIntersectionForPlaying?: ObserveFn;
onClick?: NoneToVoidFunction; onClick?: NoneToVoidFunction;
onAnimationEnd?: NoneToVoidFunction; onAnimationEnd?: NoneToVoidFunction;
}; } & ({
documentId: string;
sticker?: undefined;
} | {
sticker: ApiSticker;
documentId?: undefined;
});
const STICKER_SIZE = 20; const STICKER_SIZE = 20;
const CustomEmoji: FC<OwnProps> = ({ const CustomEmoji = ({
ref, ref,
documentId, documentId,
sticker,
className, className,
style, style,
size = STICKER_SIZE, size = STICKER_SIZE,
@ -69,14 +75,15 @@ const CustomEmoji: FC<OwnProps> = ({
observeIntersectionForPlaying, observeIntersectionForPlaying,
onClick, onClick,
onAnimationEnd, onAnimationEnd,
}) => { }: OwnProps) => {
let containerRef = useRef<HTMLDivElement>(); let containerRef = useRef<HTMLDivElement>();
if (ref) { if (ref) {
containerRef = ref; containerRef = ref;
} }
// An alternative to `withGlobal` to avoid adding numerous global containers // An alternative to `withGlobal` to avoid adding numerous global containers
const { customEmoji, canPlay } = useCustomEmoji(documentId); const { customEmoji: customEmojiFromDocumentId, canPlay } = useCustomEmoji(documentId);
const customEmoji = customEmojiFromDocumentId || sticker;
const loopCountRef = useRef(0); const loopCountRef = useRef(0);
const [shouldPlay, setShouldPlay] = useState(true); const [shouldPlay, setShouldPlay] = useState(true);

View File

@ -1,4 +1,5 @@
import type { import type {
ApiAuctionBidLevel,
ApiStarGift, ApiStarGift,
ApiStarGiftAttribute, ApiStarGiftAttribute,
ApiStarGiftAttributeBackdrop, ApiStarGiftAttributeBackdrop,
@ -122,6 +123,19 @@ export function preloadGiftAttributeStickers(attributes: ApiStarGiftAttribute[])
}); });
} }
export function getBidAuctionPosition(bidAmount: number, bidDate: number, bidLevels: ApiAuctionBidLevel[]) {
if (!bidLevels.length) return 1;
for (const level of bidLevels) {
if (level.amount < bidAmount
|| (level.amount === bidAmount && level.date >= bidDate)) {
return level.pos;
}
}
return bidLevels[bidLevels.length - 1].pos + 1;
}
export function getGiftRarityTitle( export function getGiftRarityTitle(
lang: LangFn, lang: LangFn,
rarity: ApiStarGiftAttributeModel['rarity'], rarity: ApiStarGiftAttributeModel['rarity'],

View File

@ -17,6 +17,7 @@ import { useSignalEffect } from '../../../../hooks/useSignalEffect';
import { applyAnimationState, type PaneState } from '../../../middle/hooks/useHeaderPane'; import { applyAnimationState, type PaneState } from '../../../middle/hooks/useHeaderPane';
import FrozenAccountPane from './FrozenAccountPane'; import FrozenAccountPane from './FrozenAccountPane';
import GiftAuctionPane from './GiftAuctionPane';
import SuggestionPane from './SuggestionPane'; import SuggestionPane from './SuggestionPane';
import UnconfirmedSessionPane from './UnconfirmedSessionPane'; import UnconfirmedSessionPane from './UnconfirmedSessionPane';
@ -34,6 +35,7 @@ type StateProps = {
}; };
const TOP_MARGIN = 0.5 * REM; const TOP_MARGIN = 0.5 * REM;
const ITEM_MARGIN = 0.25 * REM;
const BOTTOM_MARGIN = 0.25 * REM; const BOTTOM_MARGIN = 0.25 * REM;
const FALLBACK_PANE_STATE = { height: 0 }; const FALLBACK_PANE_STATE = { height: 0 };
@ -46,6 +48,7 @@ const ChatListPanes = ({
}: OwnProps & StateProps) => { }: OwnProps & StateProps) => {
const [getUnconfirmedSessionHeight, setUnconfirmedSessionHeight] = useSignal<PaneState>(FALLBACK_PANE_STATE); const [getUnconfirmedSessionHeight, setUnconfirmedSessionHeight] = useSignal<PaneState>(FALLBACK_PANE_STATE);
const [getFrozenAccountState, setFrozenAccountState] = useSignal<PaneState>(FALLBACK_PANE_STATE); const [getFrozenAccountState, setFrozenAccountState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
const [getGiftAuctionState, setGiftAuctionState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
const [getSuggestionState, setSuggestionState] = useSignal<PaneState>(FALLBACK_PANE_STATE); const [getSuggestionState, setSuggestionState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
const isFirstRenderRef = useRef(true); const isFirstRenderRef = useRef(true);
@ -72,20 +75,30 @@ const ChatListPanes = ({
const canShowUnconfirmedSession = !isAccountFrozen && unconfirmedSession; const canShowUnconfirmedSession = !isAccountFrozen && unconfirmedSession;
const canShowSuggestions = !isAccountFrozen && !unconfirmedSession && promoData; const canShowSuggestions = !isAccountFrozen && !unconfirmedSession && promoData;
const canShowGiftAuctions = !isAccountFrozen;
useSignalEffect(() => { useSignalEffect(() => {
const unconfirmedSessionHeight = getUnconfirmedSessionHeight(); const unconfirmedSessionHeight = getUnconfirmedSessionHeight();
const frozenAccountHeight = getFrozenAccountState(); const frozenAccountHeight = getFrozenAccountState();
const giftAuctionHeight = getGiftAuctionState();
const suggestionHeight = getSuggestionState(); const suggestionHeight = getSuggestionState();
// Keep in sync with the order of the panes in the DOM // Keep in sync with the order of the panes in the DOM
const stateArray = [unconfirmedSessionHeight, frozenAccountHeight, suggestionHeight]; const stateArray = [
{ height: TOP_MARGIN, isSpacer: true },
unconfirmedSessionHeight,
frozenAccountHeight,
giftAuctionHeight,
{ height: giftAuctionHeight.height ? ITEM_MARGIN : 0, isSpacer: true },
suggestionHeight,
{ height: BOTTOM_MARGIN, isSpacer: true },
];
const isFirstRender = isFirstRenderRef.current; const isFirstRender = isFirstRenderRef.current;
const panelsHeight = stateArray.reduce((acc, state) => acc + state.height, 0); const totalHeight = stateArray.reduce((acc, state) => acc + state.height, 0);
const totalHeight = panelsHeight ? panelsHeight + BOTTOM_MARGIN : 0; const panelsHeight = totalHeight - TOP_MARGIN - BOTTOM_MARGIN;
onHeightChange(totalHeight); onHeightChange(panelsHeight !== 0 ? totalHeight : 0);
const leftColumn = document.getElementById('LeftColumn'); const leftColumn = document.getElementById('LeftColumn');
if (!leftColumn) return; if (!leftColumn) return;
@ -93,7 +106,6 @@ const ChatListPanes = ({
applyAnimationState({ applyAnimationState({
list: stateArray, list: stateArray,
noTransition: isFirstRender, noTransition: isFirstRender,
topMargin: TOP_MARGIN,
zIndexIncrease: true, zIndexIncrease: true,
}); });
@ -102,7 +114,7 @@ const ChatListPanes = ({
'--chat-list-panes-height': `${totalHeight}px`, '--chat-list-panes-height': `${totalHeight}px`,
}); });
}); });
}, [getUnconfirmedSessionHeight, getFrozenAccountState, getSuggestionState]); }, [getUnconfirmedSessionHeight, getFrozenAccountState, getSuggestionState, getGiftAuctionState]);
if (!shouldRender) return undefined; if (!shouldRender) return undefined;
@ -124,6 +136,10 @@ const ChatListPanes = ({
unconfirmedSession={canShowUnconfirmedSession ? unconfirmedSession : undefined} unconfirmedSession={canShowUnconfirmedSession ? unconfirmedSession : undefined}
onPaneStateChange={setUnconfirmedSessionHeight} onPaneStateChange={setUnconfirmedSessionHeight}
/> />
<GiftAuctionPane
canShow={canShowGiftAuctions}
onPaneStateChange={setGiftAuctionState}
/>
<SuggestionPane <SuggestionPane
promoData={canShowSuggestions ? promoData : undefined} promoData={canShowSuggestions ? promoData : undefined}
onPaneStateChange={setSuggestionState} onPaneStateChange={setSuggestionState}

View File

@ -0,0 +1,30 @@
@use "../../../../styles/mixins";
.pane {
@include mixins.chat-list-pane;
cursor: var(--custom-cursor, pointer);
border-radius: var(--border-radius-default);
line-height: 1.25;
background-color: var(--color-background-secondary);
&:hover {
opacity: 0.85;
}
}
.title {
--emoji-size: 1.125rem;
--custom-emoji-size: var(--emoji-size);
font-size: 0.9375rem;
font-weight: var(--font-weight-semibold);
}
.subtitle {
--emoji-size: 1rem;
--custom-emoji-size: var(--emoji-size);
font-size: 0.875rem;
color: var(--color-text-secondary);
}

View File

@ -0,0 +1,36 @@
.root {
display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: auto 1fr;
}
.title {
grid-column: 1 / 2;
grid-row: 1 / 2;
}
.giftEmojis {
margin-inline-end: 0.25rem;
}
.subtitle {
grid-column: 1 / 3;
grid-row: 2 / 3;
}
.button {
grid-column: 2 / 3;
grid-row: 1 / 3;
place-self: center;
font-size: 0.875rem !important;
line-height: 1.5;
}
.buttonIcon {
font-size: 0.875rem !important;
}
.outbid {
color: var(--color-error);
}

View File

@ -0,0 +1,136 @@
import { memo, useMemo } from '@teact';
import { getActions, withGlobal } from '../../../../global';
import type { ApiStarGiftAuctionState, ApiStarGiftAuctionStateActive } from '../../../../api/types';
import type { GlobalState } from '../../../../global/types';
import buildClassName from '../../../../util/buildClassName';
import { partition } from '../../../../util/iteratees';
import { getBidAuctionPosition } from '../../../common/helpers/gifts';
import useLang from '../../../../hooks/useLang';
import useLastCallback from '../../../../hooks/useLastCallback';
import useHeaderPane, { type PaneState } from '../../../middle/hooks/useHeaderPane';
import CustomEmoji from '../../../common/CustomEmoji';
import Button from '../../../ui/Button';
import TextTimer from '../../../ui/TextTimer';
import paneStyles from './ChatPane.module.scss';
import styles from './GiftAuctionPane.module.scss';
type OwnProps = {
canShow?: boolean;
onPaneStateChange: (state: PaneState) => void;
};
type StateProps = Pick<GlobalState, 'activeGiftAuctionIds' | 'giftAuctionByGiftId'>;
const GiftAuctionPane = ({
canShow,
activeGiftAuctionIds,
giftAuctionByGiftId,
onPaneStateChange,
}: OwnProps & StateProps) => {
const { openGiftAuctionBidModal, openActiveGiftAuctionsModal } = getActions();
const isOpen = canShow && Boolean(activeGiftAuctionIds?.length);
const lang = useLang();
const { ref, shouldRender } = useHeaderPane({
isOpen,
onStateChange: onPaneStateChange,
withResizeObserver: true,
});
const [activeAuctions, winningCount, outbidCount] = useMemo(() => {
const auctions = activeGiftAuctionIds?.map((id) => giftAuctionByGiftId?.[id])
.filter((auc): auc is ApiStarGiftAuctionState => (
auc?.state.type === 'active' && Boolean(auc.userState.bidAmount)),
);
if (!auctions) return [undefined, 0, 0];
const [winning, outbid] = partition(auctions, (auction) => {
const state = auction.state as ApiStarGiftAuctionStateActive;
const position = getBidAuctionPosition(
auction.userState.bidAmount!, auction.userState.bidDate!, state.bidLevels,
);
return position <= auction.gift.giftsPerRound!;
});
return [auctions, winning.length, outbid.length];
}, [activeGiftAuctionIds, giftAuctionByGiftId]);
const activeAuctionsCount = activeAuctions?.length || 0;
const singleActiveAuction = activeAuctions?.length === 1 ? activeAuctions[0] : undefined;
function renderSubtitleText() {
if (!winningCount && !outbidCount) return undefined;
if (winningCount && !outbidCount) return lang('ChatListAuctionWinning');
if (!winningCount && outbidCount) return lang('ChatListAuctionOutbid');
return lang('ChatListAuctionMixed', { winCount: winningCount, outbidCount });
}
const handleClick = useLastCallback(() => {
if (!activeAuctions?.length) return;
if (singleActiveAuction) {
openGiftAuctionBidModal({ auctionGiftId: singleActiveAuction.gift.id });
return;
}
openActiveGiftAuctionsModal();
});
if (!shouldRender) return undefined;
return (
<div
ref={ref}
className={buildClassName(paneStyles.pane, styles.root)}
role="button"
tabIndex={0}
onClick={handleClick}
>
<div className={buildClassName(paneStyles.title, styles.title)}>
<span className={styles.giftEmojis}>
{activeAuctions?.map((auction) => (
<CustomEmoji
key={auction.gift.id}
sticker={auction.gift.sticker}
loopLimit={1}
/>
))}
</span>
{lang('ChatListAuctionTitle', { count: activeAuctionsCount }, { pluralValue: activeAuctionsCount })}
</div>
<div
className={buildClassName(
paneStyles.subtitle, styles.subtitle, !winningCount && outbidCount && styles.outbid,
)}
>
{renderSubtitleText()}
</div>
<Button
className={styles.button}
iconName={singleActiveAuction ? 'auction-filled' : undefined}
iconClassName={styles.buttonIcon}
size="tiny"
pill
onClick={handleClick}
>
{singleActiveAuction ? (
<TextTimer
endsAt={(singleActiveAuction.state as ApiStarGiftAuctionStateActive).nextRoundAt}
shouldShowZeroOnEnd
/>
) : lang('ChatListAuctionView')}
</Button>
</div>
);
};
export default memo(withGlobal<OwnProps>(
(global): Complete<StateProps> => {
return {
activeGiftAuctionIds: global.activeGiftAuctionIds,
giftAuctionByGiftId: global.giftAuctionByGiftId,
};
},
)(GiftAuctionPane));

View File

@ -1,43 +1,17 @@
@use "../../../../styles/mixins";
.root { .root {
@include mixins.chat-list-pane;
cursor: var(--custom-cursor, pointer);
display: grid; display: grid;
grid-template-columns: 1fr min-content; grid-template-columns: 1fr min-content;
grid-template-rows: min-content 1fr; grid-template-rows: min-content 1fr;
border-radius: var(--border-radius-default);
line-height: 1.25;
background-color: var(--color-background-secondary);
&:hover {
opacity: 0.85;
}
} }
.title { .title {
--emoji-size: 1.125rem;
--custom-emoji-size: var(--emoji-size);
grid-column: 1 / 2; grid-column: 1 / 2;
grid-row: 1 / 2; grid-row: 1 / 2;
font-size: 0.9375rem;
font-weight: var(--font-weight-semibold);
} }
.subtitle { .subtitle {
--emoji-size: 1rem;
--custom-emoji-size: var(--emoji-size);
grid-column: 1 / 3; grid-column: 1 / 3;
grid-row: 2 / 3; grid-row: 2 / 3;
font-size: 0.875rem;
color: var(--color-text-secondary);
} }
.closeIcon { .closeIcon {

View File

@ -4,6 +4,7 @@ import { getActions } from '../../../../global';
import type { ApiPromoData } from '../../../../api/types'; import type { ApiPromoData } from '../../../../api/types';
import type { RegularLangKey } from '../../../../types/language'; import type { RegularLangKey } from '../../../../types/language';
import buildClassName from '../../../../util/buildClassName';
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities'; import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev'; import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
@ -13,6 +14,7 @@ import useHeaderPane, { type PaneState } from '../../../middle/hooks/useHeaderPa
import Icon from '../../../common/icons/Icon'; import Icon from '../../../common/icons/Icon';
import paneStyles from './ChatPane.module.scss';
import styles from './SuggestionPane.module.scss'; import styles from './SuggestionPane.module.scss';
type OwnProps = { type OwnProps = {
@ -90,13 +92,13 @@ const SuggestionPane = ({ promoData, onPaneStateChange }: OwnProps) => {
return ( return (
<div <div
ref={ref} ref={ref}
className={styles.root} className={buildClassName(paneStyles.pane, styles.root)}
role="button" role="button"
tabIndex={0} tabIndex={0}
onClick={handleClick} onClick={handleClick}
> >
<div className={styles.title}>{title}</div> <div className={buildClassName(paneStyles.title, styles.title)}>{title}</div>
<div className={styles.subtitle}>{message}</div> <div className={buildClassName(paneStyles.subtitle, styles.subtitle)}>{message}</div>
<Icon name="close" className={styles.closeIcon} onClick={handleDismiss} /> <Icon name="close" className={styles.closeIcon} onClick={handleDismiss} />
</div> </div>
); );

View File

@ -266,6 +266,7 @@ const Main = ({
loadContentSettings, loadContentSettings,
loadGiftAuction, loadGiftAuction,
loadPromoData, loadPromoData,
loadActiveGiftAuctions,
} = getActions(); } = getActions();
if (DEBUG && !DEBUG_isLogged) { if (DEBUG && !DEBUG_isLogged) {
@ -347,6 +348,7 @@ const Main = ({
loadRestrictedEmojiStickers(); loadRestrictedEmojiStickers();
loadQuickReplies(); loadQuickReplies();
loadTimezones(); loadTimezones();
loadActiveGiftAuctions();
} }
}, [isMasterTab, isSynced, isAppConfigLoaded, isAccountFrozen]); }, [isMasterTab, isSynced, isAppConfigLoaded, isAccountFrozen]);

View File

@ -21,6 +21,7 @@ export interface PaneState {
element?: HTMLElement; element?: HTMLElement;
height: number; height: number;
isOpen?: boolean; isOpen?: boolean;
isSpacer?: boolean;
} }
// Max slide transition duration // Max slide transition duration
@ -130,18 +131,17 @@ export function applyAnimationState({
list, list,
noTransition = false, noTransition = false,
zIndexIncrease, zIndexIncrease,
topMargin = 0,
}: { }: {
list: PaneState[]; list: PaneState[];
noTransition?: boolean; noTransition?: boolean;
zIndexIncrease?: boolean; zIndexIncrease?: boolean;
topMargin?: number;
}) { }) {
let cumulativeHeight = 0; let cumulativeHeight = 0;
for (let i = 0; i < list.length; i++) { for (let i = 0; i < list.length; i++) {
const state = list[i]; const state = list[i];
const element = state.element; const element = state.element;
if (!element) { if (!element) {
if (state.isSpacer) cumulativeHeight += state.height;
continue; continue;
} }
@ -149,7 +149,7 @@ export function applyAnimationState({
const apply = () => { const apply = () => {
setExtraStyles(element, { setExtraStyles(element, {
transform: `translateY(${state.isOpen ? shiftPx : `calc(${shiftPx} - ${topMargin}px - 100%)`})`, transform: `translateY(${state.isOpen ? shiftPx : `calc(${shiftPx} - 100%)`})`,
zIndex: String(-i), zIndex: String(-i),
transition: noTransition ? 'none' : '', transition: noTransition ? 'none' : '',
}); });
@ -158,7 +158,7 @@ export function applyAnimationState({
if (!element.dataset.isPanelOpen && state.isOpen && !noTransition) { if (!element.dataset.isPanelOpen && state.isOpen && !noTransition) {
// Start animation right above its final position // Start animation right above its final position
setExtraStyles(element, { setExtraStyles(element, {
transform: `translateY(calc(${shiftPx} - ${topMargin}px - 100%))`, transform: `translateY(calc(${shiftPx} - 100%))`,
zIndex: String(zIndexIncrease ? i : -i), zIndex: String(zIndexIncrease ? i : -i),
transition: 'none', transition: 'none',
}); });

View File

@ -20,6 +20,7 @@ import DeleteAccountModal from './deleteAccount/DeleteAccountModal.async';
import EmojiStatusAccessModal from './emojiStatusAccess/EmojiStatusAccessModal.async'; import EmojiStatusAccessModal from './emojiStatusAccess/EmojiStatusAccessModal.async';
import FrozenAccountModal from './frozenAccount/FrozenAccountModal.async'; import FrozenAccountModal from './frozenAccount/FrozenAccountModal.async';
import AboutStarGiftModal from './gift/AboutStarGiftModal.async'; import AboutStarGiftModal from './gift/AboutStarGiftModal.async';
import ActiveGiftAuctionsModal from './gift/auction/ActiveGiftAuctionsModal.async';
import GiftAuctionAcquiredModal from './gift/auction/GiftAuctionAcquiredModal.async'; import GiftAuctionAcquiredModal from './gift/auction/GiftAuctionAcquiredModal.async';
import GiftAuctionBidModal from './gift/auction/GiftAuctionBidModal.async'; import GiftAuctionBidModal from './gift/auction/GiftAuctionBidModal.async';
import GiftAuctionChangeRecipientModal from './gift/auction/GiftAuctionChangeRecipientModal.async'; import GiftAuctionChangeRecipientModal from './gift/auction/GiftAuctionChangeRecipientModal.async';
@ -107,6 +108,7 @@ type ModalKey = keyof Pick<TabState,
'giftAuctionInfoModal' | 'giftAuctionInfoModal' |
'giftAuctionChangeRecipientModal' | 'giftAuctionChangeRecipientModal' |
'giftAuctionAcquiredModal' | 'giftAuctionAcquiredModal' |
'activeGiftAuctionsModal' |
'starGiftPriceDecreaseInfoModal' | 'starGiftPriceDecreaseInfoModal' |
'aboutStarGiftModal' | 'aboutStarGiftModal' |
'monetizationVerificationModal' | 'monetizationVerificationModal' |
@ -182,6 +184,7 @@ const MODALS: ModalRegistry = {
giftAuctionInfoModal: GiftAuctionInfoModal, giftAuctionInfoModal: GiftAuctionInfoModal,
giftAuctionChangeRecipientModal: GiftAuctionChangeRecipientModal, giftAuctionChangeRecipientModal: GiftAuctionChangeRecipientModal,
giftAuctionAcquiredModal: GiftAuctionAcquiredModal, giftAuctionAcquiredModal: GiftAuctionAcquiredModal,
activeGiftAuctionsModal: ActiveGiftAuctionsModal,
starGiftPriceDecreaseInfoModal: StarGiftPriceDecreaseInfoModal, starGiftPriceDecreaseInfoModal: StarGiftPriceDecreaseInfoModal,
aboutStarGiftModal: AboutStarGiftModal, aboutStarGiftModal: AboutStarGiftModal,
monetizationVerificationModal: VerificationMonetizationModal, monetizationVerificationModal: VerificationMonetizationModal,

View File

@ -0,0 +1,14 @@
import type { OwnProps } from './ActiveGiftAuctionsModal';
import { Bundles } from '../../../../util/moduleLoader';
import useModuleLoader from '../../../../hooks/useModuleLoader';
const ActiveGiftAuctionsModalAsync = (props: OwnProps) => {
const { modal } = props;
const ActiveGiftAuctionsModal = useModuleLoader(Bundles.Stars, 'ActiveGiftAuctionsModal', !modal);
return ActiveGiftAuctionsModal ? <ActiveGiftAuctionsModal {...props} /> : undefined;
};
export default ActiveGiftAuctionsModalAsync;

View File

@ -0,0 +1,24 @@
.gift {
--custom-emoji-size: 2rem;
margin-inline-end: 0.5rem;
}
.timer {
margin-inline-start: 0.25rem;
}
.noActive {
color: var(--color-text-secondary);
text-align: center;
}
.content {
--border-radius-default: 0;
padding: 0 !important;
}
.dialog {
overflow: hidden !important;
}

View File

@ -0,0 +1,123 @@
import { memo, useMemo } from '@teact';
import { getActions, withGlobal } from '../../../../global';
import type { ApiStarGiftAuctionState, ApiStarGiftAuctionStateActive } from '../../../../api/types';
import type { TabState } from '../../../../global/types';
import { formatStarsAsIcon } from '../../../../util/localization/format';
import { getBidAuctionPosition } from '../../../common/helpers/gifts';
import { REM } from '../../../common/helpers/mediaDimensions';
import useLang from '../../../../hooks/useLang';
import useLastCallback from '../../../../hooks/useLastCallback';
import CustomEmoji from '../../../common/CustomEmoji';
import Button from '../../../ui/Button';
import ListItem from '../../../ui/ListItem';
import Modal from '../../../ui/Modal';
import TextTimer from '../../../ui/TextTimer';
import styles from './ActiveGiftAuctionsModal.module.scss';
export type OwnProps = {
modal: TabState['activeGiftAuctionsModal'];
};
type StateProps = {
activeGiftAuctionIds?: string[];
giftAuctionByGiftId?: Record<string, ApiStarGiftAuctionState>;
};
const ICON_SIZE = 2 * REM;
const ActiveGiftAuctionsModal = ({
modal, activeGiftAuctionIds, giftAuctionByGiftId,
}: OwnProps & StateProps) => {
const { closeActiveGiftAuctionsModal } = getActions();
const lang = useLang();
const activeAuctions = useMemo(() => {
return activeGiftAuctionIds?.map((id) => giftAuctionByGiftId?.[id])
.filter((auc): auc is ApiStarGiftAuctionState => (
auc?.state.type === 'active' && Boolean(auc.userState.bidAmount)
));
}, [activeGiftAuctionIds, giftAuctionByGiftId]);
return (
<Modal
isOpen={Boolean(modal)}
onClose={closeActiveGiftAuctionsModal}
hasCloseButton
isCondensedHeader
title={lang('GiftAuctionActiveTitle')}
dialogClassName={styles.dialog}
contentClassName={styles.content}
>
{activeAuctions?.length
? activeAuctions.map((auction) => <ActiveAuctionItem key={auction.gift.id} auction={auction} />)
: <div className={styles.noActive}>{lang('GiftAuctionNoActive')}</div>}
</Modal>
);
};
function ActiveAuctionItem({ auction }: { auction: ApiStarGiftAuctionState }) {
const lang = useLang();
const { openGiftAuctionBidModal, closeActiveGiftAuctionsModal } = getActions();
const { userState, gift } = auction;
const state = auction.state as ApiStarGiftAuctionStateActive;
const bidPosition = useMemo(() => {
return getBidAuctionPosition(userState.bidAmount!, userState.bidDate!, state.bidLevels);
}, [userState, state.bidLevels]);
const handleClick = useLastCallback(() => {
openGiftAuctionBidModal({ auctionGiftId: gift.id });
closeActiveGiftAuctionsModal();
});
return (
<ListItem
leftElement={<CustomEmoji className={styles.gift} sticker={gift.sticker} size={ICON_SIZE} loopLimit={1} />}
rightElement={(
<Button
size="tiny"
pill
fluid
iconName="auction-filled"
onClick={handleClick}
>
{lang('GiftAuctionListRaiseBid')}
<TextTimer className={styles.timer} endsAt={state.nextRoundAt} shouldShowZeroOnEnd />
</Button>
)}
multiline
onClick={handleClick}
>
<div className="title">
{lang('GiftAuctionListRound', {
current: lang.number(state.currentRound),
total: lang.number(state.totalRounds),
})}
</div>
<div className="subtitle">
{lang('GiftAuctionBidPosition', {
amount: formatStarsAsIcon(lang, userState.bidAmount!, { noStyles: true }),
position: lang.number(bidPosition),
}, {
withNodes: true,
})}
</div>
</ListItem>
);
}
export default memo(withGlobal<OwnProps>(
(global): Complete<StateProps> => {
const { activeGiftAuctionIds, giftAuctionByGiftId } = global;
return {
activeGiftAuctionIds,
giftAuctionByGiftId,
};
},
)(ActiveGiftAuctionsModal));

View File

@ -10,6 +10,7 @@ import type { TabState } from '../../../../global/types';
import { selectPeer, selectTabState } from '../../../../global/selectors'; import { selectPeer, selectTabState } from '../../../../global/selectors';
import { formatStarsAsIcon } from '../../../../util/localization/format'; import { formatStarsAsIcon } from '../../../../util/localization/format';
import { getBidAuctionPosition } from '../../../common/helpers/gifts';
import renderText from '../../../common/helpers/renderText'; import renderText from '../../../common/helpers/renderText';
import { useTransitionActiveKey } from '../../../../hooks/animations/useTransitionActiveKey'; import { useTransitionActiveKey } from '../../../../hooks/animations/useTransitionActiveKey';
@ -191,14 +192,7 @@ const GiftAuctionBidModal = ({
const { bidLevels } = activeState; const { bidLevels } = activeState;
const userBidDate = userState?.bidDate || Number.MAX_SAFE_INTEGER; const userBidDate = userState?.bidDate || Number.MAX_SAFE_INTEGER;
for (const level of bidLevels) { return getBidAuctionPosition(selectedBidAmount, userBidDate, 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]); }, [selectedBidAmount, activeState, userState?.bidDate]);
function renderInfoCards() { function renderInfoCards() {

View File

@ -112,7 +112,6 @@ const GiftTransferModal = ({
hasCloseButton hasCloseButton
shouldAdaptToSearch shouldAdaptToSearch
withFixedHeight withFixedHeight
ignoreFreeze
> >
<PeerPicker<Categories> <PeerPicker<Categories>
itemIds={displayIds} itemIds={displayIds}

View File

@ -370,8 +370,8 @@
font-size: 0.875rem; font-size: 0.875rem;
&.round { &.round {
width: 1.875rem; width: 1.75rem;
height: 1.875rem; height: 1.75rem;
border-radius: 50%; border-radius: 50%;
} }
@ -380,8 +380,8 @@
} }
&.pill { &.pill {
height: 1.875rem; height: 1.75rem;
padding: 0.3125rem 1rem; padding: 0.25rem 0.5rem;
border-radius: 1rem; border-radius: 1rem;
font-size: 1rem; font-size: 1rem;
} }

View File

@ -45,7 +45,6 @@ export type OwnProps = {
dialogRef?: ElementRef<HTMLDivElement>; dialogRef?: ElementRef<HTMLDivElement>;
isLowStackPriority?: boolean; isLowStackPriority?: boolean;
dialogContent?: React.ReactNode; dialogContent?: React.ReactNode;
ignoreFreeze?: boolean;
moreMenuItems?: TeactNode; moreMenuItems?: TeactNode;
onClose: () => void; onClose: () => void;
onCloseAnimationEnd?: () => void; onCloseAnimationEnd?: () => void;

View File

@ -9,6 +9,7 @@ import useForceUpdate from '../../hooks/useForceUpdate';
import AnimatedCounter from '../common/AnimatedCounter'; import AnimatedCounter from '../common/AnimatedCounter';
type OwnProps = { type OwnProps = {
className?: string;
endsAt: number; endsAt: number;
shouldShowZeroOnEnd?: boolean; shouldShowZeroOnEnd?: boolean;
onEnd?: NoneToVoidFunction; onEnd?: NoneToVoidFunction;
@ -16,7 +17,7 @@ type OwnProps = {
const UPDATE_FREQUENCY = 500; // Sometimes second gets skipped if using 1000 const UPDATE_FREQUENCY = 500; // Sometimes second gets skipped if using 1000
const TextTimer = ({ endsAt, shouldShowZeroOnEnd, onEnd }: OwnProps) => { const TextTimer = ({ className, endsAt, shouldShowZeroOnEnd, onEnd }: OwnProps) => {
const forceUpdate = useForceUpdate(); const forceUpdate = useForceUpdate();
const serverTime = getServerTime(); const serverTime = getServerTime();
@ -35,8 +36,8 @@ const TextTimer = ({ endsAt, shouldShowZeroOnEnd, onEnd }: OwnProps) => {
const time = formatMediaDuration(timeLeft); const time = formatMediaDuration(timeLeft);
const timeParts = time.split(':'); const timeParts = time.split(':');
const timeCounter = ( return (
<span style="font-variant-numeric: tabular-nums;"> <span className={className} style="font-variant-numeric: tabular-nums;">
{timeParts.map((part, index) => ( {timeParts.map((part, index) => (
<> <>
{index > 0 && ':'} {index > 0 && ':'}
@ -45,12 +46,6 @@ const TextTimer = ({ endsAt, shouldShowZeroOnEnd, onEnd }: OwnProps) => {
))} ))}
</span> </span>
); );
return (
<span>
{timeCounter}
</span>
);
}; };
export default TextTimer; export default TextTimer;

View File

@ -612,6 +612,7 @@ addActionHandler('loadGiftAuction', async (global, _actions, payload): Promise<v
global = getGlobal(); global = getGlobal();
global = replaceGiftAuction(global, auctionState); global = replaceGiftAuction(global, auctionState);
setGlobal(global); setGlobal(global);
}); });
@ -735,3 +736,19 @@ addActionHandler('declineStarGiftOffer', async (global, actions, payload): Promi
shouldDecline: true, shouldDecline: true,
}); });
}); });
addActionHandler('loadActiveGiftAuctions', async (global, actions, payload): Promise<void> => {
const result = await callApi('fetchStarGiftActiveAuctions');
if (!result) return;
global = getGlobal();
result.auctions.forEach((auction) => {
global = replaceGiftAuction(global, auction);
});
global = {
...global,
activeGiftAuctionIds: result.auctions.map((auction) => auction.gift.id),
};
setGlobal(global);
});

View File

@ -260,6 +260,11 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
case 'updateStarGiftAuctionUserState': { case 'updateStarGiftAuctionUserState': {
const { giftId, userState } = update; const { giftId, userState } = update;
if (!global.giftAuctionByGiftId?.[giftId]) {
actions.loadActiveGiftAuctions();
return;
}
global = updateGiftAuctionUserState(global, giftId, userState); global = updateGiftAuctionUserState(global, giftId, userState);
setGlobal(global); setGlobal(global);

View File

@ -676,3 +676,13 @@ addActionHandler('resetSelectedGiftCollection', (global, actions, payload): Acti
peerId, shouldRefresh: true, tabId: tabState.id, peerId, shouldRefresh: true, tabId: tabState.id,
}); });
}); });
addActionHandler('openActiveGiftAuctionsModal', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload || {};
return updateTabState(global, {
activeGiftAuctionsModal: true,
}, tabId);
});
addTabStateResetterAction('closeActiveGiftAuctionsModal', 'activeGiftAuctionsModal');

View File

@ -7,7 +7,7 @@ import type {
import type { GlobalState } from '../types'; import type { GlobalState } from '../types';
import { getCurrentTabId } from '../../util/establishMultitabRole'; import { getCurrentTabId } from '../../util/establishMultitabRole';
import { omit } from '../../util/iteratees'; import { omit, unique } from '../../util/iteratees';
import { updateTabState } from './tabs'; import { updateTabState } from './tabs';
export function removeGiftInfoOriginalDetails<T extends GlobalState>( export function removeGiftInfoOriginalDetails<T extends GlobalState>(
@ -74,7 +74,11 @@ export function updateGiftAuction<T extends GlobalState>(
...global, ...global,
giftAuctionByGiftId: { giftAuctionByGiftId: {
...global.giftAuctionByGiftId, ...global.giftAuctionByGiftId,
[giftId]: auctionState, [giftId]: {
...auctionState,
// Keep timeout in case of updates from active auction list, as those do not have this field
timeout: auctionState.timeout || currentAuction.timeout,
},
}, },
}; };
} }
@ -123,6 +127,21 @@ export function updateGiftAuctionUserState<T extends GlobalState>(
return global; return global;
} }
const currentBidDate = giftAuction.userState.bidDate;
if (userState.bidDate !== currentBidDate) {
if (userState.bidDate) {
global = {
...global,
activeGiftAuctionIds: unique([...(global.activeGiftAuctionIds || []), giftId]),
};
} else {
global = {
...global,
activeGiftAuctionIds: global.activeGiftAuctionIds?.filter((id) => id !== giftId),
};
}
}
return { return {
...global, ...global,
giftAuctionByGiftId: { giftAuctionByGiftId: {
@ -140,11 +159,32 @@ export function replaceGiftAuction<T extends GlobalState>(
auctionState: ApiStarGiftAuctionState, auctionState: ApiStarGiftAuctionState,
): T { ): T {
const giftId = auctionState.gift.id; const giftId = auctionState.gift.id;
const previousAuction = global.giftAuctionByGiftId?.[giftId];
if (previousAuction) {
if (previousAuction.userState.bidDate !== auctionState.userState.bidDate) {
if (auctionState.userState.bidDate) {
global = {
...global,
activeGiftAuctionIds: unique([...(global.activeGiftAuctionIds || []), giftId]),
};
} else {
global = {
...global,
activeGiftAuctionIds: global.activeGiftAuctionIds?.filter((id) => id !== giftId),
};
}
}
}
return { return {
...global, ...global,
giftAuctionByGiftId: { giftAuctionByGiftId: {
...global.giftAuctionByGiftId, ...global.giftAuctionByGiftId,
[giftId]: auctionState, [giftId]: {
...auctionState,
timeout: auctionState.timeout || previousAuction?.timeout,
},
}, },
}; };
} }
@ -158,9 +198,11 @@ export function removeGiftAuction<T extends GlobalState>(
} }
const rest = omit(global.giftAuctionByGiftId, [giftId]); const rest = omit(global.giftAuctionByGiftId, [giftId]);
const activeGiftAuctionIds = global.activeGiftAuctionIds?.filter((id) => id !== giftId);
return { return {
...global, ...global,
giftAuctionByGiftId: Object.keys(rest).length ? rest : undefined, giftAuctionByGiftId: Object.keys(rest).length ? rest : undefined,
activeGiftAuctionIds,
}; };
} }

View File

@ -2714,6 +2714,9 @@ export interface ActionPayloads {
gift: ApiStarGiftUnique; gift: ApiStarGiftUnique;
} & WithTabId; } & WithTabId;
closeGiftInfoValueModal: WithTabId | undefined; closeGiftInfoValueModal: WithTabId | undefined;
loadActiveGiftAuctions: undefined;
openActiveGiftAuctionsModal: WithTabId | undefined;
closeActiveGiftAuctionsModal: WithTabId | undefined;
openGiftAuctionModal: { openGiftAuctionModal: {
gift: ApiStarGiftRegular; gift: ApiStarGiftRegular;
} & WithTabId; } & WithTabId;

View File

@ -331,6 +331,7 @@ export type GlobalState = {
starGiftCollections?: { starGiftCollections?: {
byPeerId: Record<string, ApiStarGiftCollection[]>; byPeerId: Record<string, ApiStarGiftCollection[]>;
}; };
activeGiftAuctionIds?: string[];
giftAuctionByGiftId?: Record<string, ApiStarGiftAuctionState>; giftAuctionByGiftId?: Record<string, ApiStarGiftAuctionState>;
stickers: { stickers: {

View File

@ -937,6 +937,8 @@ export type TabState = {
acquiredGifts?: ApiStarGiftAuctionAcquiredGift[]; acquiredGifts?: ApiStarGiftAuctionAcquiredGift[];
}; };
activeGiftAuctionsModal?: true;
starGiftPriceDecreaseInfoModal?: { starGiftPriceDecreaseInfoModal?: {
prices: ApiStarGiftUpgradePrice[]; prices: ApiStarGiftUpgradePrice[];
currentPrice: number; currentPrice: number;

View File

@ -1895,6 +1895,7 @@ payments.getUniqueStarGiftValueInfo#4365af6b slug:string = payments.UniqueStarGi
payments.checkCanSendGift#c0c4edc9 gift_id:long = payments.CheckCanSendGiftResult; payments.checkCanSendGift#c0c4edc9 gift_id:long = payments.CheckCanSendGiftResult;
payments.getStarGiftAuctionState#5c9ff4d6 auction:InputStarGiftAuction version:int = payments.StarGiftAuctionState; payments.getStarGiftAuctionState#5c9ff4d6 auction:InputStarGiftAuction version:int = payments.StarGiftAuctionState;
payments.getStarGiftAuctionAcquiredGifts#6ba2cbec gift_id:long = payments.StarGiftAuctionAcquiredGifts; payments.getStarGiftAuctionAcquiredGifts#6ba2cbec gift_id:long = payments.StarGiftAuctionAcquiredGifts;
payments.getStarGiftActiveAuctions#a5d0514d hash:long = payments.StarGiftActiveAuctions;
payments.resolveStarGiftOffer#e9ce781c flags:# decline:flags.0?true offer_msg_id:int = Updates; payments.resolveStarGiftOffer#e9ce781c flags:# decline:flags.0?true offer_msg_id:int = Updates;
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall; phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;

View File

@ -355,6 +355,7 @@
"payments.getStarGiftCollections", "payments.getStarGiftCollections",
"payments.getStarGiftAuctionState", "payments.getStarGiftAuctionState",
"payments.getStarGiftAuctionAcquiredGifts", "payments.getStarGiftAuctionAcquiredGifts",
"payments.getStarGiftActiveAuctions",
"payments.resolveStarGiftOffer", "payments.resolveStarGiftOffer",
"langpack.getLangPack", "langpack.getLangPack",
"langpack.getStrings", "langpack.getStrings",

View File

@ -1844,6 +1844,9 @@ export interface LangPair {
'GiftAuctionTapToBidMore': undefined; 'GiftAuctionTapToBidMore': undefined;
'GiftAuctionLearnMoreAboutGifts': undefined; 'GiftAuctionLearnMoreAboutGifts': undefined;
'GiftAuctionLearnMoreMenuItem': undefined; 'GiftAuctionLearnMoreMenuItem': undefined;
'GiftAuctionListRaiseBid': undefined;
'GiftAuctionActiveTitle': undefined;
'GiftAuctionNoActive': undefined;
'StarGiftInfoTitle': undefined; 'StarGiftInfoTitle': undefined;
'StarGiftInfoSubtitle': undefined; 'StarGiftInfoSubtitle': undefined;
'StarGiftInfoUniqueTitle': undefined; 'StarGiftInfoUniqueTitle': undefined;
@ -1913,6 +1916,9 @@ export interface LangPair {
'SettingsDataClearMediaCache': undefined; 'SettingsDataClearMediaCache': undefined;
'SettingsDataClearMediaCacheDescription': undefined; 'SettingsDataClearMediaCacheDescription': undefined;
'SettingsDataClearMediaDone': undefined; 'SettingsDataClearMediaDone': undefined;
'ChatListAuctionWinning': undefined;
'ChatListAuctionOutbid': undefined;
'ChatListAuctionView': undefined;
} }
export interface LangPairWithVariables<V = LangVariable> { export interface LangPairWithVariables<V = LangVariable> {
@ -3312,6 +3318,14 @@ export interface LangPairWithVariables<V = LangVariable> {
'GiftAuctionWonNotification': { 'GiftAuctionWonNotification': {
'gift': V; 'gift': V;
}; };
'GiftAuctionBidPosition': {
'amount': V;
'position': V;
};
'GiftAuctionListRound': {
'current': V;
'total': V;
};
'SettingsPasskeyUsedAt': { 'SettingsPasskeyUsedAt': {
'date': V; 'date': V;
}; };
@ -3346,6 +3360,10 @@ export interface LangPairWithVariables<V = LangVariable> {
'status': V; 'status': V;
'onlineCount': V; 'onlineCount': V;
}; };
'ChatListAuctionMixed': {
'winCount': V;
'outbidCount': V;
};
} }
export interface LangPairPlural { export interface LangPairPlural {
@ -3785,6 +3803,9 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
'AttachmentSendFile': { 'AttachmentSendFile': {
'count': V; 'count': V;
}; };
'ChatListAuctionTitle': {
'count': V;
};
} }
export type RegularLangKey = keyof LangPair; export type RegularLangKey = keyof LangPair;
export type RegularLangKeyWithVariables = keyof LangPairWithVariables; export type RegularLangKeyWithVariables = keyof LangPairWithVariables;

View File

@ -49,11 +49,15 @@ export function formatTonAsIcon(
} }
export function formatStarsAsIcon(lang: LangFn, amount: number | string, options?: { export function formatStarsAsIcon(lang: LangFn, amount: number | string, options?: {
asFont?: boolean; className?: string; containerClassName?: string; }) { asFont?: boolean;
const { asFont, className, containerClassName } = options || {}; className?: string;
containerClassName?: string;
noStyles?: boolean;
}) {
const { asFont, className, containerClassName, noStyles } = options || {};
const icon = asFont const icon = asFont
? <Icon name="star" className={buildClassName('star-amount-icon', className)} /> ? <Icon name="star" className={buildClassName(!noStyles && 'star-amount-icon', className)} />
: <StarIcon type="gold" className={buildClassName('star-amount-icon', className)} size="adaptive" />; : <StarIcon type="gold" className={buildClassName(!noStyles && 'star-amount-icon', className)} size="adaptive" />;
if (containerClassName) { if (containerClassName) {
return ( return (