Gift Upgrade: Support gift upgrade price info modal (#6511)
This commit is contained in:
parent
9e08371b9c
commit
a0bf570dd6
@ -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) || [],
|
||||
};
|
||||
}
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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}";
|
||||
@ -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';
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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<OwnProps> = ({
|
||||
progress = 0,
|
||||
isPrimary,
|
||||
isNegative,
|
||||
isInverted,
|
||||
shouldSkipGradient,
|
||||
animationDirection = 'none',
|
||||
className,
|
||||
}) => {
|
||||
@ -89,7 +93,8 @@ const PremiumProgress: FC<OwnProps> = ({
|
||||
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<OwnProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
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<OwnProps> = ({
|
||||
hasFloatingBadge && styles.withBadge,
|
||||
isPrimary && styles.primary,
|
||||
isNegative && styles.negative,
|
||||
isInverted && styles.inverted,
|
||||
shouldSkipGradient && styles.noGradient,
|
||||
shouldAnimateCaptions && styles.transitioning,
|
||||
isCycling && styles.cycling,
|
||||
className,
|
||||
|
||||
@ -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<TabState,
|
||||
'locationAccessModal' |
|
||||
'aboutAdsModal' |
|
||||
'giftUpgradeModal' |
|
||||
'starGiftPriceDecreaseInfoModal' |
|
||||
'monetizationVerificationModal' |
|
||||
'giftWithdrawModal' |
|
||||
'preparedMessageModal' |
|
||||
@ -156,6 +158,7 @@ const MODALS: ModalRegistry = {
|
||||
locationAccessModal: LocationAccessModal,
|
||||
aboutAdsModal: AboutAdsModal,
|
||||
giftUpgradeModal: GiftUpgradeModal,
|
||||
starGiftPriceDecreaseInfoModal: StarGiftPriceDecreaseInfoModal,
|
||||
monetizationVerificationModal: VerificationMonetizationModal,
|
||||
giftWithdrawModal: GiftWithdrawModal,
|
||||
giftStatusInfoModal: GiftStatusInfoModal,
|
||||
|
||||
@ -30,6 +30,7 @@ type OwnProps = {
|
||||
buttonText?: string;
|
||||
className?: string;
|
||||
contentClassName?: string;
|
||||
tableClassName?: string;
|
||||
hasBackdrop?: boolean;
|
||||
onClose: NoneToVoidFunction;
|
||||
onButtonClick?: NoneToVoidFunction;
|
||||
@ -49,6 +50,7 @@ const TableInfoModal = ({
|
||||
buttonText,
|
||||
className,
|
||||
contentClassName,
|
||||
tableClassName,
|
||||
hasBackdrop,
|
||||
onClose,
|
||||
onButtonClick,
|
||||
@ -82,7 +84,7 @@ const TableInfoModal = ({
|
||||
<Avatar peer={headerAvatarPeer} size="jumbo" className={styles.avatar} />
|
||||
)}
|
||||
{header}
|
||||
<div className={styles.table}>
|
||||
<div className={buildClassName(styles.table, tableClassName)}>
|
||||
{tableData?.map(([label, value]) => (
|
||||
<>
|
||||
{Boolean(label) && <div className={buildClassName(styles.cell, styles.title)}>{label}</div>}
|
||||
|
||||
@ -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<OwnProps> = (props) => {
|
||||
const { modal } = props;
|
||||
const StarGiftPriceDecreaseInfoModal = useModuleLoader(Bundles.Stars, 'StarGiftPriceDecreaseInfoModal', !modal);
|
||||
|
||||
return StarGiftPriceDecreaseInfoModal ? <StarGiftPriceDecreaseInfoModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default StarGiftPriceDecreaseInfoModalAsync;
|
||||
@ -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;
|
||||
}
|
||||
@ -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 (
|
||||
<div className={styles.footer}>
|
||||
<p className={styles.footerText}>{lang('UpgradeCostDrops')}</p>
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
iconName="understood"
|
||||
iconClassName={styles.understoodIcon}
|
||||
>
|
||||
{lang('ButtonUnderstood')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}, [lang, isOpen, handleClose]);
|
||||
|
||||
if (!tableData || !renderingModal) return undefined;
|
||||
|
||||
const { currentPrice, minPrice, maxPrice } = renderingModal;
|
||||
const progress = maxPrice !== minPrice ? (currentPrice - minPrice) / (maxPrice - minPrice) : 0;
|
||||
|
||||
const header = (
|
||||
<div className={styles.header}>
|
||||
<PremiumProgress
|
||||
leftText={formatStarsAsText(lang, maxPrice)}
|
||||
rightText={formatStarsAsText(lang, minPrice)}
|
||||
floatingBadgeText={formatStarsAsText(lang, currentPrice)}
|
||||
floatingBadgeIcon="star"
|
||||
progress={progress}
|
||||
isInverted
|
||||
shouldSkipGradient
|
||||
className={styles.progress}
|
||||
/>
|
||||
<p className={styles.headerTitle}>{lang('StarGiftUpgradeCostModalTitle')}</p>
|
||||
<p className={styles.headerHint}>{lang('StarGiftUpgradeCostHint')}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<TableInfoModal
|
||||
isOpen={isOpen}
|
||||
onClose={handleClose}
|
||||
header={header}
|
||||
tableData={tableData}
|
||||
tableClassName={buildClassName(styles.table, 'custom-scroll')}
|
||||
footer={footer}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(StarGiftPriceDecreaseInfoModal);
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 ? (
|
||||
<span>
|
||||
<Icon name="star" className="star-amount-icon" />
|
||||
<AnimatedCounter text={lang.number(upgradeStars)} />
|
||||
</span>
|
||||
) : 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 = (
|
||||
<UniqueGiftHeader
|
||||
@ -178,12 +210,32 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
|
||||
/>
|
||||
)}
|
||||
<Button className={styles.footerButton} isShiny onClick={handleUpgrade}>
|
||||
{gift.alreadyPaidUpgradeStars
|
||||
? lang('GeneralConfirm')
|
||||
: isPrepaid
|
||||
? lang('GiftPayForUpgradeButton', { amount: formattedPrice }, { withNodes: true })
|
||||
: lang('GiftUpgradeButton', { amount: formattedPrice }, { withNodes: true })}
|
||||
<div className={styles.buttonContent}>
|
||||
<div>
|
||||
{gift.alreadyPaidUpgradeStars
|
||||
? lang('GeneralConfirm')
|
||||
: isPrepaid
|
||||
? lang('GiftPayForUpgradeButton', { amount: formattedPriceElement }, { withNodes: true })
|
||||
: lang('GiftUpgradeButton', { amount: formattedPriceElement }, { withNodes: true })}
|
||||
</div>
|
||||
{hasPriceDecreaseInfo && (
|
||||
<div className={styles.priceDecreaseTimer}>
|
||||
{lang('StarGiftPriceDecreaseTimer', {
|
||||
timer: <TextTimer endsAt={nextPriceDate} onEnd={handleTimerEnd} />,
|
||||
}, { withNodes: true })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
{hasPriceDecreaseInfo && (
|
||||
<Link
|
||||
className={styles.link}
|
||||
isPrimary
|
||||
onClick={handleOpenPriceInfo}
|
||||
>
|
||||
{lang('StarGiftPriceDecreaseInfoLink')}
|
||||
</Link>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@ -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 (
|
||||
<TableAboutModal
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
} from '../../../config';
|
||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import { buildCollectionByCallback, buildCollectionByKey } from '../../../util/iteratees';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import { RESALE_GIFTS_LIMIT } from '../../../limits';
|
||||
import { areInputSavedGiftsEqual, getRequestInputSavedStarGift } from '../../helpers/payments';
|
||||
@ -494,11 +495,24 @@ addActionHandler('openGiftUpgradeModal', async (global, actions, payload): Promi
|
||||
giftId, gift, peerId, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
const samples = await callApi('fetchStarGiftUpgradePreview', {
|
||||
const preview = await callApi('fetchStarGiftUpgradePreview', {
|
||||
giftId,
|
||||
});
|
||||
|
||||
if (!samples) return;
|
||||
if (!preview) return;
|
||||
|
||||
const serverTime = getServerTime();
|
||||
const filteredPrices = preview.prices.filter((price) => 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<void> => {
|
||||
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<void> => {
|
||||
const { gift, peerId, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
|
||||
@ -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 || {};
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
8
src/types/language.d.ts
vendored
8
src/types/language.d.ts
vendored
@ -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<V = LangVariable> {
|
||||
@ -3073,6 +3078,9 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'StealthModeComposerPlaceholder': {
|
||||
'timer': V;
|
||||
};
|
||||
'StarGiftPriceDecreaseTimer': {
|
||||
'timer': V;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LangPairPlural {
|
||||
|
||||
@ -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',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user