diff --git a/src/api/gramjs/apiBuilders/gifts.ts b/src/api/gramjs/apiBuilders/gifts.ts index 587909afb..c95815107 100644 --- a/src/api/gramjs/apiBuilders/gifts.ts +++ b/src/api/gramjs/apiBuilders/gifts.ts @@ -9,6 +9,8 @@ import type { ApiStarGiftAttributeCounter, ApiStarGiftAttributeId, ApiStarGiftCollection, + ApiStarGiftUpgradePreview, + ApiStarGiftUpgradePrice, ApiTypeResaleStarGifts, } from '../../types'; @@ -315,3 +317,20 @@ export function buildApiStarGiftCollection(collection: GramJs.StarGiftCollection hash: hash.toString(), }; } + +export function buildApiStarGiftUpgradePrice(price: GramJs.StarGiftUpgradePrice): ApiStarGiftUpgradePrice { + return { + date: price.date, + upgradeStars: toJSNumber(price.upgradeStars), + }; +} + +export function buildApiStarGiftUpgradePreview( + result: GramJs.payments.StarGiftUpgradePreview, +): ApiStarGiftUpgradePreview { + return { + sampleAttributes: result.sampleAttributes.map(buildApiStarGiftAttribute).filter(Boolean), + prices: result.prices?.map(buildApiStarGiftUpgradePrice) || [], + nextPrices: result.nextPrices?.map(buildApiStarGiftUpgradePrice) || [], + }; +} diff --git a/src/api/gramjs/methods/stars.ts b/src/api/gramjs/methods/stars.ts index 0b49580ab..92e086d7e 100644 --- a/src/api/gramjs/methods/stars.ts +++ b/src/api/gramjs/methods/stars.ts @@ -14,8 +14,14 @@ import { buildApiChatFromPreview } from '../apiBuilders/chats'; import { buildApiFormattedText, } from '../apiBuilders/common'; -import { buildApiResaleGifts, buildApiSavedStarGift, buildApiStarGift, - buildApiStarGiftAttribute, buildApiStarGiftCollection, buildInputResaleGiftsAttributes } from '../apiBuilders/gifts'; +import { + buildApiResaleGifts, + buildApiSavedStarGift, + buildApiStarGift, + buildApiStarGiftCollection, + buildApiStarGiftUpgradePreview, + buildInputResaleGiftsAttributes, +} from '../apiBuilders/gifts'; import { buildApiCurrencyAmount, buildApiStarsGiftOptions, @@ -403,7 +409,7 @@ export async function fetchStarGiftUpgradePreview({ return undefined; } - return result.sampleAttributes.map(buildApiStarGiftAttribute).filter(Boolean); + return buildApiStarGiftUpgradePreview(result); } export function upgradeStarGift({ diff --git a/src/api/types/stars.ts b/src/api/types/stars.ts index 480b80823..ca1ab9f6d 100644 --- a/src/api/types/stars.ts +++ b/src/api/types/stars.ts @@ -89,6 +89,17 @@ export interface ApiStarGiftAttributeOriginalDetails { export type ApiStarGiftAttribute = ApiStarGiftAttributeModel | ApiStarGiftAttributePattern | ApiStarGiftAttributeBackdrop | ApiStarGiftAttributeOriginalDetails; +export interface ApiStarGiftUpgradePrice { + date: number; + upgradeStars: number; +} + +export interface ApiStarGiftUpgradePreview { + sampleAttributes: ApiStarGiftAttribute[]; + prices: ApiStarGiftUpgradePrice[]; + nextPrices: ApiStarGiftUpgradePrice[]; +} + export interface ApiSavedStarGift { isNameHidden?: boolean; isUnsaved?: boolean; diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index bdbb9b111..09dfb113a 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -2398,3 +2398,9 @@ "StealthModeButtonToStory" = "Enable and Open the Story"; "StealthModeButtonRecharge" = "Available in {timer}"; "StealthModeComposerPlaceholder" = "Stealth Mode active – {timer}"; +"UsersWhoUpgradeFirst" = "Users who upgrade their gifts first get collectibles with shorter numbers."; +"UpgradeCostDrops" = "Upgrade cost drops every minute."; +"StarGiftPriceDecreaseInfoLink" = "See how price will decrease >"; +"StarGiftUpgradeCostModalTitle" = "Upgrade Cost"; +"StarGiftUpgradeCostHint" = "Users who upgrade their gifts first get collectibles with shorter numbers."; +"StarGiftPriceDecreaseTimer" = "Price decreases in {timer}"; \ No newline at end of file diff --git a/src/bundles/stars.ts b/src/bundles/stars.ts index 74b093bb2..755488a0b 100644 --- a/src/bundles/stars.ts +++ b/src/bundles/stars.ts @@ -12,6 +12,7 @@ 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 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'; export { default as GiftTransferModal } from '../components/modals/gift/transfer/GiftTransferModal'; diff --git a/src/components/common/PremiumProgress.module.scss b/src/components/common/PremiumProgress.module.scss index eef90bf9e..8000ec791 100644 --- a/src/components/common/PremiumProgress.module.scss +++ b/src/components/common/PremiumProgress.module.scss @@ -1,5 +1,7 @@ /* stylelint-disable plugin/no-low-performance-animation-properties */ +$progress-color: #7e85ff; + .root { --percent: calc(var(--progress, 0.5) * 100%); @@ -81,7 +83,7 @@ color: #ffffff; - background-color: #7E85FF; + background-color: $progress-color; transition: width 0.25s ease-in-out; @@ -110,7 +112,7 @@ display: inline-block; - color: #7E85FF; + color: $progress-color; transition: left 0.3s ease; } @@ -251,6 +253,34 @@ } } +.inverted { + .positiveLayer, + .positiveProgress { + --multiplier: calc(1 / var(--positive-progress) - 1); + + right: 0; + left: auto; + background-position: 100% 0; + background-size: calc(100% / var(--positive-progress)) 100%; + + .left { + left: calc(-100% * var(--multiplier) + 0.75rem); + } + + .right { + right: 0.75rem; + } + } +} + +.noGradient { + .positiveLayer, + .positiveProgress { + background-color: $progress-color; + background-image: none; + } +} + .transitioning { .left, .right { diff --git a/src/components/common/PremiumProgress.tsx b/src/components/common/PremiumProgress.tsx index 1bdd2eec4..a5bcd000e 100644 --- a/src/components/common/PremiumProgress.tsx +++ b/src/components/common/PremiumProgress.tsx @@ -31,6 +31,8 @@ type OwnProps = { progress?: number; isPrimary?: boolean; isNegative?: boolean; + isInverted?: boolean; + shouldSkipGradient?: boolean; animationDirection?: AnimationDirection; className?: string; }; @@ -43,6 +45,8 @@ const PremiumProgress: FC = ({ progress = 0, isPrimary, isNegative, + isInverted, + shouldSkipGradient, animationDirection = 'none', className, }) => { @@ -89,7 +93,8 @@ const PremiumProgress: FC = ({ const minBadgeShift = halfBadgeWidth; const maxBadgeShift = parentWidth - halfBadgeWidth; const halfBeakWidth = BEAK_WIDTH_PX / 2; - const currentShift = isNegative ? (1 - badgeProgress) * parentWidth : badgeProgress * parentWidth; + const effectiveProgress = (isInverted || isNegative) ? (1 - badgeProgress) : badgeProgress; + const currentShift = effectiveProgress * parentWidth; let safeShift = Math.max(minBadgeShift, Math.min(currentShift, maxBadgeShift)); if (currentShift < CORNER_BEAK_THRESHOLD) { @@ -107,7 +112,7 @@ const PremiumProgress: FC = ({ } }; - useEffect(updateBadgePosition, [badgeProgress, badgeWidth, isNegative, CORNER_BEAK_THRESHOLD]); + useEffect(updateBadgePosition, [badgeProgress, badgeWidth, isNegative, isInverted, CORNER_BEAK_THRESHOLD]); useResizeObserver(parentContainerRef, updateBadgePosition); @@ -260,6 +265,8 @@ const PremiumProgress: FC = ({ hasFloatingBadge && styles.withBadge, isPrimary && styles.primary, isNegative && styles.negative, + isInverted && styles.inverted, + shouldSkipGradient && styles.noGradient, shouldAnimateCaptions && styles.transitioning, isCycling && styles.cycling, className, diff --git a/src/components/modals/ModalContainer.tsx b/src/components/modals/ModalContainer.tsx index 2255170b8..d35136030 100644 --- a/src/components/modals/ModalContainer.tsx +++ b/src/components/modals/ModalContainer.tsx @@ -25,6 +25,7 @@ import GiftLockedModal from './gift/locked/GiftLockedModal.async'; import GiftDescriptionRemoveModal from './gift/message/GiftDescriptionRemoveModal.async'; import GiftRecipientPicker from './gift/recipient/GiftRecipientPicker.async'; import GiftResalePriceComposerModal from './gift/resale/GiftResalePriceComposerModal.async'; +import StarGiftPriceDecreaseInfoModal from './gift/StarGiftPriceDecreaseInfoModal.async'; import GiftStatusInfoModal from './gift/status/GiftStatusInfoModal.async'; import GiftTransferConfirmModal from './gift/transfer/GiftTransferConfirmModal.async'; import GiftTransferModal from './gift/transfer/GiftTransferModal.async'; @@ -92,6 +93,7 @@ type ModalKey = keyof Pick )} {header} -
+
{tableData?.map(([label, value]) => ( <> {Boolean(label) &&
{label}
} diff --git a/src/components/modals/gift/StarGiftPriceDecreaseInfoModal.async.tsx b/src/components/modals/gift/StarGiftPriceDecreaseInfoModal.async.tsx new file mode 100644 index 000000000..d5fadbf83 --- /dev/null +++ b/src/components/modals/gift/StarGiftPriceDecreaseInfoModal.async.tsx @@ -0,0 +1,16 @@ +import type { FC } from '../../../lib/teact/teact'; + +import type { OwnProps } from './StarGiftPriceDecreaseInfoModal'; + +import { Bundles } from '../../../util/moduleLoader'; + +import useModuleLoader from '../../../hooks/useModuleLoader'; + +const StarGiftPriceDecreaseInfoModalAsync: FC = (props) => { + const { modal } = props; + const StarGiftPriceDecreaseInfoModal = useModuleLoader(Bundles.Stars, 'StarGiftPriceDecreaseInfoModal', !modal); + + return StarGiftPriceDecreaseInfoModal ? : undefined; +}; + +export default StarGiftPriceDecreaseInfoModalAsync; diff --git a/src/components/modals/gift/StarGiftPriceDecreaseInfoModal.module.scss b/src/components/modals/gift/StarGiftPriceDecreaseInfoModal.module.scss new file mode 100644 index 000000000..f744a5582 --- /dev/null +++ b/src/components/modals/gift/StarGiftPriceDecreaseInfoModal.module.scss @@ -0,0 +1,49 @@ +.header { + text-align: center; +} + +.headerTitle { + margin-top: 1.5rem; + margin-bottom: 0; + font-size: 1.5rem; + font-weight: var(--font-weight-semibold); +} + +.headerHint { + margin-top: 0.375rem; + margin-bottom: 0.5rem; + + font-size: 1rem; + line-height: 1.375rem; + text-wrap: balance; +} + +.starIconContainer { + display: inline-flex; + align-items: center; + line-height: 1rem; +} + +.progress { + margin: 4.5rem auto 1.5rem; +} + +.table { + overflow-y: auto; + max-height: 19.25rem; +} + +.footerText { + font-size: 0.875rem; + color: var(--color-text-secondary); + text-align: center; +} + +.footer { + display: flex; + flex-direction: column; +} + +.understoodIcon { + font-size: 1.25rem; +} diff --git a/src/components/modals/gift/StarGiftPriceDecreaseInfoModal.tsx b/src/components/modals/gift/StarGiftPriceDecreaseInfoModal.tsx new file mode 100644 index 000000000..cbdff8c48 --- /dev/null +++ b/src/components/modals/gift/StarGiftPriceDecreaseInfoModal.tsx @@ -0,0 +1,95 @@ +import { memo, useMemo } from '../../../lib/teact/teact'; +import { getActions } from '../../../global'; + +import type { TabState } from '../../../global/types'; + +import buildClassName from '../../../util/buildClassName'; +import { formatDateTimeToString } from '../../../util/dates/dateFormat'; +import { formatStarsAsIcon, formatStarsAsText } from '../../../util/localization/format'; + +import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; + +import PremiumProgress from '../../common/PremiumProgress'; +import Button from '../../ui/Button'; +import TableInfoModal, { type TableData } from '../common/TableInfoModal'; + +import styles from './StarGiftPriceDecreaseInfoModal.module.scss'; + +export type OwnProps = { + modal: TabState['starGiftPriceDecreaseInfoModal']; +}; + +const StarGiftPriceDecreaseInfoModal = ({ modal }: OwnProps) => { + const { closeStarGiftPriceDecreaseInfoModal } = getActions(); + + const lang = useLang(); + + const isOpen = Boolean(modal); + const renderingModal = useCurrentOrPrev(modal); + + const handleClose = useLastCallback(() => { + closeStarGiftPriceDecreaseInfoModal(); + }); + + const tableData = useMemo(() => { + if (!renderingModal) return undefined; + const { prices } = renderingModal; + return prices.map((price): TableData[number] => [ + formatDateTimeToString(price.date * 1000, lang.code, true, undefined, true), + formatStarsAsIcon(lang, price.upgradeStars, { containerClassName: styles.starIconContainer }), + ]); + }, [lang, renderingModal]); + + const footer = useMemo(() => { + if (!isOpen) return undefined; + return ( +
+

{lang('UpgradeCostDrops')}

+ +
+ ); + }, [lang, isOpen, handleClose]); + + if (!tableData || !renderingModal) return undefined; + + const { currentPrice, minPrice, maxPrice } = renderingModal; + const progress = maxPrice !== minPrice ? (currentPrice - minPrice) / (maxPrice - minPrice) : 0; + + const header = ( +
+ +

{lang('StarGiftUpgradeCostModalTitle')}

+

{lang('StarGiftUpgradeCostHint')}

+
+ ); + + return ( + + ); +}; + +export default memo(StarGiftPriceDecreaseInfoModal); diff --git a/src/components/modals/gift/upgrade/GiftUpgradeModal.module.scss b/src/components/modals/gift/upgrade/GiftUpgradeModal.module.scss index 4c5774a78..d05bb5b8e 100644 --- a/src/components/modals/gift/upgrade/GiftUpgradeModal.module.scss +++ b/src/components/modals/gift/upgrade/GiftUpgradeModal.module.scss @@ -8,3 +8,14 @@ .footerButton { margin-top: 0.5rem; } + +.link { + display: flex; + justify-content: center; +} + +.priceDecreaseTimer { + font-size: 0.875rem; + text-transform: none; + opacity: 0.6; +} diff --git a/src/components/modals/gift/upgrade/GiftUpgradeModal.tsx b/src/components/modals/gift/upgrade/GiftUpgradeModal.tsx index e220b1db1..fbf886b74 100644 --- a/src/components/modals/gift/upgrade/GiftUpgradeModal.tsx +++ b/src/components/modals/gift/upgrade/GiftUpgradeModal.tsx @@ -9,7 +9,6 @@ import type { ApiStarGiftAttributeBackdrop, ApiStarGiftAttributeModel, ApiStarGiftAttributePattern, - ApiStarGiftRegular, } from '../../../../api/types'; import type { TabState } from '../../../../global/types'; import { ApiMediaFormat } from '../../../../api/types'; @@ -17,7 +16,6 @@ import { ApiMediaFormat } from '../../../../api/types'; import { getStickerMediaHash } from '../../../../global/helpers'; import { getPeerTitle } from '../../../../global/helpers/peers'; import { selectPeer } from '../../../../global/selectors'; -import { formatStarsAsIcon } from '../../../../util/localization/format'; import { fetch } from '../../../../util/mediaLoader'; import useInterval from '../../../../hooks/schedulers/useInterval'; @@ -25,8 +23,12 @@ import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev'; import useLang from '../../../../hooks/useLang'; import useLastCallback from '../../../../hooks/useLastCallback'; +import AnimatedCounter from '../../../common/AnimatedCounter'; +import Icon from '../../../common/icons/Icon'; import Button from '../../../ui/Button'; import Checkbox from '../../../ui/Checkbox'; +import Link from '../../../ui/Link'; +import TextTimer from '../../../ui/TextTimer'; import TableAboutModal, { type TableAboutData } from '../../common/TableAboutModal'; import UniqueGiftHeader from '../UniqueGiftHeader'; @@ -49,7 +51,14 @@ type Attributes = { const PREVIEW_UPDATE_INTERVAL = 3000; const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => { - const { closeGiftUpgradeModal, closeGiftInfoModal, upgradeGift, upgradePrepaidGift } = getActions(); + const { + closeGiftUpgradeModal, + closeGiftInfoModal, + upgradeGift, + upgradePrepaidGift, + openStarGiftPriceDecreaseInfoModal, + shiftGiftUpgradeNextPrice, + } = getActions(); const isOpen = Boolean(modal); const renderingModal = useCurrentOrPrev(modal); @@ -64,15 +73,20 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => { const handleClose = useLastCallback(() => closeGiftUpgradeModal()); + const handleTimerEnd = useLastCallback(() => { + shiftGiftUpgradeNextPrice(); + }); + + const nextPrice = renderingModal?.nextPrices?.[0]; + const nextPriceDate = nextPrice?.date; + const upgradeStars = renderingModal?.currentUpgradeStars; + const handleUpgrade = useLastCallback(() => { const gift = renderingModal?.gift; if (!gift) return; - const regularGift = gift.gift.type === 'starGift' ? gift.gift : undefined; - if (isPrepaid && gift.prepaidUpgradeHash && renderingRecipient) { - const upgradeStars = regularGift?.upgradeStars; if (!upgradeStars) return; upgradePrepaidGift({ @@ -90,7 +104,7 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => { upgradeGift({ gift: gift.inputGift, shouldKeepOriginalDetails, - upgradeStars: !gift.alreadyPaidUpgradeStars ? regularGift?.upgradeStars : undefined, + upgradeStars: !gift.alreadyPaidUpgradeStars ? upgradeStars : undefined, }); handleClose(); }); @@ -100,6 +114,17 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => { setPreviewAttributes(getRandomAttributes(renderingModal.sampleAttributes, previewAttributes)); }); + const handleOpenPriceInfo = useLastCallback(() => { + if (!renderingModal?.prices) return; + + openStarGiftPriceDecreaseInfoModal({ + prices: renderingModal.prices, + currentPrice: upgradeStars || 0, + minPrice: renderingModal.minPrice || 0, + maxPrice: renderingModal.maxPrice || 0, + }); + }); + useInterval(updatePreviewAttributes, isOpen ? PREVIEW_UPDATE_INTERVAL : undefined, true); useEffect(() => { @@ -123,6 +148,13 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => { }); }, [renderingModal?.sampleAttributes]); + const formattedPriceElement = useMemo(() => (upgradeStars ? ( + + + + + ) : undefined), [lang, upgradeStars]); + const modalData = useMemo(() => { if (!previewAttributes || !isOpen) { return undefined; @@ -147,9 +179,9 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => { ? lang('GiftPeerUpgradeText', { peer: getPeerTitle(lang, renderingRecipient) }) : lang('GiftUpgradeTextOwn'); - const formattedPrice = gift - ? formatStarsAsIcon(lang, (gift.gift as ApiStarGiftRegular).upgradeStars!, { asFont: true }) - : undefined; + const hasPriceDecreaseInfo = Boolean(nextPriceDate) + && Boolean(renderingModal?.prices?.length) + && !gift?.alreadyPaidUpgradeStars; const header = ( { /> )} + {hasPriceDecreaseInfo && ( + + {lang('StarGiftPriceDecreaseInfoLink')} + + )} )}
@@ -196,7 +248,9 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => { }; }, [previewAttributes, isOpen, lang, renderingRecipient, renderingModal?.gift, - shouldKeepOriginalDetails, isPrepaid]); + shouldKeepOriginalDetails, isPrepaid, + renderingModal?.prices?.length, + nextPriceDate, formattedPriceElement]); return ( price.date > serverTime); + const filteredNextPrices = preview.nextPrices.filter((price) => price.date > serverTime); + + const passedPrices = preview.nextPrices.filter((price) => price.date <= serverTime); + const regularGift = gift?.gift.type === 'starGift' ? gift.gift : undefined; + const currentUpgradeStars = passedPrices.length + ? passedPrices[passedPrices.length - 1].upgradeStars + : regularGift?.upgradeStars; + + const maxPrice = preview.prices[0]?.upgradeStars; + const minPrice = preview.prices.at(-1)?.upgradeStars; global = getGlobal(); @@ -506,13 +520,65 @@ addActionHandler('openGiftUpgradeModal', async (global, actions, payload): Promi giftUpgradeModal: { recipientId: peerId, gift, - sampleAttributes: samples, + sampleAttributes: preview.sampleAttributes, + prices: filteredPrices, + nextPrices: filteredNextPrices, + currentUpgradeStars, + minPrice, + maxPrice, }, }, tabId); setGlobal(global); }); +addActionHandler('shiftGiftUpgradeNextPrice', async (global, _actions, payload): Promise => { + const { tabId = getCurrentTabId() } = payload || {}; + const tabState = selectTabState(global, tabId); + const giftUpgradeModal = tabState?.giftUpgradeModal; + if (!giftUpgradeModal?.nextPrices?.length) return; + + const currentUpgradeStars = giftUpgradeModal.nextPrices[0].upgradeStars; + const newNextPrices = giftUpgradeModal.nextPrices.slice(1); + + if (newNextPrices.length) { + global = updateTabState(global, { + giftUpgradeModal: { + ...giftUpgradeModal, + nextPrices: newNextPrices, + currentUpgradeStars, + }, + }, tabId); + setGlobal(global); + + return; + } + + const gift = giftUpgradeModal.gift?.gift; + const giftId = gift?.type === 'starGift' ? gift.id : undefined; + if (!giftId) return; + + const preview = await callApi('fetchStarGiftUpgradePreview', { giftId }); + if (!preview) return; + + const serverTime = getServerTime(); + const filteredNextPrices = preview.nextPrices.filter((price) => price.date > serverTime); + + global = getGlobal(); + const currentTabState = selectTabState(global, tabId); + const currentModal = currentTabState?.giftUpgradeModal; + if (!currentModal) return; + + global = updateTabState(global, { + giftUpgradeModal: { + ...currentModal, + nextPrices: filteredNextPrices, + currentUpgradeStars, + }, + }, tabId); + setGlobal(global); +}); + addActionHandler('toggleSavedGiftPinned', async (global, actions, payload): Promise => { const { gift, peerId, tabId = getCurrentTabId() } = payload; diff --git a/src/global/actions/ui/stars.ts b/src/global/actions/ui/stars.ts index 35ac3825b..b176d3a29 100644 --- a/src/global/actions/ui/stars.ts +++ b/src/global/actions/ui/stars.ts @@ -387,6 +387,23 @@ addTabStateResetterAction('closeGiftResalePriceComposerModal', 'giftResalePriceC addTabStateResetterAction('closeGiftUpgradeModal', 'giftUpgradeModal'); +addActionHandler('openStarGiftPriceDecreaseInfoModal', (global, actions, payload): ActionReturnType => { + const { + prices, currentPrice, minPrice, maxPrice, tabId = getCurrentTabId(), + } = payload; + + return updateTabState(global, { + starGiftPriceDecreaseInfoModal: { + prices, + currentPrice, + minPrice, + maxPrice, + }, + }, tabId); +}); + +addTabStateResetterAction('closeStarGiftPriceDecreaseInfoModal', 'starGiftPriceDecreaseInfoModal'); + addActionHandler('openGiftWithdrawModal', (global, actions, payload): ActionReturnType => { const { gift, tabId = getCurrentTabId() } = payload || {}; diff --git a/src/global/types/actions.ts b/src/global/types/actions.ts index fa46513a5..addeb28d8 100644 --- a/src/global/types/actions.ts +++ b/src/global/types/actions.ts @@ -46,6 +46,7 @@ import type { ApiStarGift, ApiStarGiftAttributeOriginalDetails, ApiStarGiftUnique, + ApiStarGiftUpgradePrice, ApiStarsSubscription, ApiStarsTransaction, ApiSticker, @@ -2628,6 +2629,15 @@ export interface ActionPayloads { gift?: ApiSavedStarGift; } & WithTabId; closeGiftUpgradeModal: WithTabId | undefined; + shiftGiftUpgradeNextPrice: WithTabId | undefined; + + openStarGiftPriceDecreaseInfoModal: { + prices: ApiStarGiftUpgradePrice[]; + currentPrice: number; + minPrice: number; + maxPrice: number; + } & WithTabId; + closeStarGiftPriceDecreaseInfoModal: WithTabId | undefined; upgradeGift: { gift: ApiInputSavedStarGift; shouldKeepOriginalDetails?: boolean; diff --git a/src/global/types/tabState.ts b/src/global/types/tabState.ts index 4f5a04199..1c38d2fe4 100644 --- a/src/global/types/tabState.ts +++ b/src/global/types/tabState.ts @@ -44,6 +44,7 @@ import type { ApiStarGiftAttributeCounter, ApiStarGiftAttributeOriginalDetails, ApiStarGiftUnique, + ApiStarGiftUpgradePrice, ApiStarGiveawayOption, ApiStarsSubscription, ApiStarsTransaction, @@ -867,6 +868,11 @@ export type TabState = { sampleAttributes: ApiStarGiftAttribute[]; recipientId?: string; gift?: ApiSavedStarGift; + prices?: ApiStarGiftUpgradePrice[]; + nextPrices?: ApiStarGiftUpgradePrice[]; + currentUpgradeStars?: number; + minPrice?: number; + maxPrice?: number; }; giftWithdrawModal?: { @@ -879,6 +885,13 @@ export type TabState = { emojiStatus: ApiEmojiStatusCollectible; }; + starGiftPriceDecreaseInfoModal?: { + prices: ApiStarGiftUpgradePrice[]; + currentPrice: number; + minPrice: number; + maxPrice: number; + }; + suggestedStatusModal?: { botId: string; webAppKey?: string; diff --git a/src/types/language.d.ts b/src/types/language.d.ts index 69a8c3007..798050d01 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -1782,6 +1782,11 @@ export interface LangPair { 'StealthModeButtonPremium': undefined; 'StealthModeButton': undefined; 'StealthModeButtonToStory': undefined; + 'UsersWhoUpgradeFirst': undefined; + 'UpgradeCostDrops': undefined; + 'StarGiftPriceDecreaseInfoLink': undefined; + 'StarGiftUpgradeCostModalTitle': undefined; + 'StarGiftUpgradeCostHint': undefined; } export interface LangPairWithVariables { @@ -3073,6 +3078,9 @@ export interface LangPairWithVariables { 'StealthModeComposerPlaceholder': { 'timer': V; }; + 'StarGiftPriceDecreaseTimer': { + 'timer': V; + }; } export interface LangPairPlural { diff --git a/src/util/dates/dateFormat.ts b/src/util/dates/dateFormat.ts index 8db50687a..ec141ed4a 100644 --- a/src/util/dates/dateFormat.ts +++ b/src/util/dates/dateFormat.ts @@ -398,13 +398,13 @@ export function formatDateToString( export function formatDateTimeToString( datetime: Date | number, locale = 'en-US', noSeconds?: boolean, - timeFormat?: TimeFormat, + timeFormat?: TimeFormat, noYear?: boolean, ) { const date = typeof datetime === 'number' ? new Date(datetime) : datetime; return date.toLocaleString( locale, { - year: 'numeric', + year: noYear ? undefined : 'numeric', month: 'short', day: 'numeric', hour: 'numeric',