TelegramPWA/src/components/modals/gift/upgrade/GiftUpgradeModal.tsx

299 lines
9.9 KiB
TypeScript

import {
memo, useEffect, useMemo, useState,
} from '../../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../../global';
import type {
ApiPeer,
ApiStarGiftAttribute,
ApiStarGiftAttributeBackdrop,
ApiStarGiftAttributeModel,
ApiStarGiftAttributePattern,
} from '../../../../api/types';
import type { TabState } from '../../../../global/types';
import { ApiMediaFormat } from '../../../../api/types';
import { getStickerMediaHash } from '../../../../global/helpers';
import { getPeerTitle } from '../../../../global/helpers/peers';
import { selectPeer } from '../../../../global/selectors';
import { fetch } from '../../../../util/mediaLoader';
import useInterval from '../../../../hooks/schedulers/useInterval';
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';
import styles from './GiftUpgradeModal.module.scss';
export type OwnProps = {
modal: TabState['giftUpgradeModal'];
};
type StateProps = {
recipient?: ApiPeer;
};
type Attributes = {
model: ApiStarGiftAttributeModel;
pattern: ApiStarGiftAttributePattern;
backdrop: ApiStarGiftAttributeBackdrop;
};
const PREVIEW_UPDATE_INTERVAL = 3000;
const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
const {
closeGiftUpgradeModal,
closeGiftInfoModal,
upgradeGift,
upgradePrepaidGift,
openStarGiftPriceDecreaseInfoModal,
shiftGiftUpgradeNextPrice,
} = getActions();
const isOpen = Boolean(modal);
const renderingModal = useCurrentOrPrev(modal);
const renderingRecipient = useCurrentOrPrev(recipient);
const [shouldKeepOriginalDetails, setShouldKeepOriginalDetails] = useState(false);
const isPrepaid = Boolean(renderingModal?.gift?.prepaidUpgradeHash);
const [previewAttributes, setPreviewAttributes] = useState<Attributes | undefined>();
const lang = useLang();
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;
if (isPrepaid && gift.prepaidUpgradeHash && renderingRecipient) {
if (!upgradeStars) return;
upgradePrepaidGift({
peerId: renderingRecipient.id,
hash: gift.prepaidUpgradeHash,
stars: upgradeStars,
});
handleClose();
closeGiftInfoModal();
return;
}
if (!gift?.inputGift) return;
upgradeGift({
gift: gift.inputGift,
shouldKeepOriginalDetails,
upgradeStars: !gift.alreadyPaidUpgradeStars ? upgradeStars : undefined,
});
handleClose();
});
const updatePreviewAttributes = useLastCallback(() => {
if (!renderingModal?.sampleAttributes) return;
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(() => {
if (isOpen && renderingModal?.sampleAttributes) {
updatePreviewAttributes();
}
}, [isOpen, renderingModal?.sampleAttributes]);
// Preload stickers and patterns
useEffect(() => {
const attributes = renderingModal?.sampleAttributes;
if (!attributes) return;
const patternStickers = attributes.filter((attr): attr is ApiStarGiftAttributeModel => attr.type === 'pattern')
.map((attr) => attr.sticker);
const modelStickers = attributes.filter((attr): attr is ApiStarGiftAttributeModel => attr.type === 'model')
.map((attr) => attr.sticker);
const mediaHashes = [...patternStickers, ...modelStickers].map((sticker) => getStickerMediaHash(sticker, 'full'));
mediaHashes.forEach((hash) => {
fetch(hash, ApiMediaFormat.BlobUrl);
});
}, [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;
}
const gift = renderingModal?.gift;
const userName = renderingRecipient ? getPeerTitle(lang, renderingRecipient) : lang('ActionFallbackUser');
const listItemData = (renderingRecipient ? [
['diamond', lang('GiftUpgradeUniqueTitle'), lang('GiftPeerUpgradeUniqueDescription', { user: userName })],
['trade', lang('GiftUpgradeTransferableTitle'),
lang('GiftPeerUpgradeTransferableDescription', { user: userName })],
['auction', lang('GiftUpgradeTradeableTitle'), lang('GiftPeerUpgradeTradeableDescription', { user: userName })],
] : [
['diamond', lang('GiftUpgradeUniqueTitle'), lang('GiftUpgradeUniqueDescription')],
['trade', lang('GiftUpgradeTransferableTitle'), lang('GiftUpgradeTransferableDescription')],
['auction', lang('GiftUpgradeTradeableTitle'), lang('GiftUpgradeTradeableDescription')],
]) satisfies TableAboutData;
const subtitle = renderingRecipient
? lang('GiftPeerUpgradeText', { peer: getPeerTitle(lang, renderingRecipient) })
: lang('GiftUpgradeTextOwn');
const hasPriceDecreaseInfo = Boolean(nextPriceDate)
&& Boolean(renderingModal?.prices?.length)
&& !gift?.alreadyPaidUpgradeStars;
const header = (
<UniqueGiftHeader
modelAttribute={previewAttributes.model}
backdropAttribute={previewAttributes.backdrop}
patternAttribute={previewAttributes.pattern}
title={lang('GiftUpgradeTitle')}
subtitle={subtitle}
/>
);
const footer = (
<div className={styles.footer}>
{!gift && (
<Button className={styles.footerButton} onClick={handleClose}>
{lang('OK')}
</Button>
)}
{gift && (
<>
{!isPrepaid && (
<Checkbox
label={lang('GiftUpgradeKeepDetails')}
onCheck={setShouldKeepOriginalDetails}
checked={shouldKeepOriginalDetails}
/>
)}
<Button className={styles.footerButton} isShiny onClick={handleUpgrade}>
<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>
);
return {
listItemData,
header,
footer,
};
}, [previewAttributes, isOpen, lang,
renderingRecipient, renderingModal?.gift,
shouldKeepOriginalDetails, isPrepaid,
renderingModal?.prices?.length,
nextPriceDate, formattedPriceElement]);
return (
<TableAboutModal
isOpen={isOpen}
header={modalData?.header}
footer={modalData?.footer}
listItemData={modalData?.listItemData}
hasBackdrop
onClose={handleClose}
/>
);
};
export default memo(withGlobal<OwnProps>(
(global, { modal }): Complete<StateProps> => {
const recipientId = modal?.recipientId;
const recipient = recipientId ? selectPeer(global, recipientId) : undefined;
return {
recipient,
};
},
)(GiftUpgradeModal));
function getRandomAttributes(list: ApiStarGiftAttribute[], previousSelection?: Attributes): Attributes {
const models = list.filter((attr): attr is ApiStarGiftAttributeModel => (
attr.type === 'model' && attr.name !== previousSelection?.model.name
));
const patterns = list.filter((attr): attr is ApiStarGiftAttributePattern => (
attr.type === 'pattern' && attr.name !== previousSelection?.pattern.name
));
const backdrops = list.filter((attr): attr is ApiStarGiftAttributeBackdrop => (
attr.type === 'backdrop' && attr.name !== previousSelection?.backdrop.name
));
const randomModel = models[Math.floor(Math.random() * models.length)];
const randomPattern = patterns[Math.floor(Math.random() * patterns.length)];
const randomBackdrop = backdrops[Math.floor(Math.random() * backdrops.length)];
return {
model: randomModel,
pattern: randomPattern,
backdrop: randomBackdrop,
};
}