PremiumMainModal: Render all subscription options (#4129)

This commit is contained in:
Alexander Zinchuk 2023-12-28 14:38:10 +01:00
parent d2b834d1a6
commit 3e96262038
8 changed files with 87 additions and 25 deletions

View File

@ -194,7 +194,7 @@ function buildApiPremiumSubscriptionOption(option: GramJs.PremiumSubscriptionOpt
isCurrent: current, isCurrent: current,
canPurchaseUpgrade, canPurchaseUpgrade,
currency, currency,
amount: amount.toString(), amount: amount.toJSNumber(),
botUrl, botUrl,
months, months,
}; };

View File

@ -75,7 +75,7 @@ export interface ApiPremiumSubscriptionOption {
canPurchaseUpgrade?: boolean; canPurchaseUpgrade?: boolean;
months: number; months: number;
currency: string; currency: string;
amount: string; amount: number;
botUrl: string; botUrl: string;
} }

View File

@ -22,7 +22,7 @@ import Avatar from '../../common/Avatar';
import Button from '../../ui/Button'; import Button from '../../ui/Button';
import Link from '../../ui/Link'; import Link from '../../ui/Link';
import Modal from '../../ui/Modal'; import Modal from '../../ui/Modal';
import GiftOption from './GiftOption'; import PremiumSubscriptionOption from './PremiumSubscriptionOption';
import styles from './GiftPremiumModal.module.scss'; import styles from './GiftPremiumModal.module.scss';
@ -141,7 +141,7 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
<div className={styles.options}> <div className={styles.options}>
{renderedGifts?.map((gift) => ( {renderedGifts?.map((gift) => (
<GiftOption <PremiumSubscriptionOption
key={gift.amount} key={gift.amount}
option={gift} option={gift}
fullMonthlyAmount={fullMonthlyAmount} fullMonthlyAmount={fullMonthlyAmount}

View File

@ -168,3 +168,11 @@
height: 100%; height: 100%;
} }
} }
.subscriptionOptions {
width: 100%;
}
.subscriptionOption {
margin: 0.8125rem;
}

View File

@ -5,7 +5,7 @@ import React, {
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { import type {
ApiPremiumPromo, ApiSticker, ApiStickerSet, ApiUser, ApiPremiumPromo, ApiPremiumSubscriptionOption, ApiSticker, ApiStickerSet, ApiUser,
} from '../../../api/types'; } from '../../../api/types';
import type { GlobalState } from '../../../global/types'; import type { GlobalState } from '../../../global/types';
@ -36,6 +36,7 @@ import PremiumFeatureModal, {
PREMIUM_FEATURE_SECTIONS, PREMIUM_FEATURE_SECTIONS,
PREMIUM_FEATURE_TITLES, PREMIUM_FEATURE_TITLES,
} from './PremiumFeatureModal'; } from './PremiumFeatureModal';
import PremiumSubscriptionOption from './PremiumSubscriptionOption';
import styles from './PremiumMainModal.module.scss'; import styles from './PremiumMainModal.module.scss';
@ -133,6 +134,7 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
const lang = useLang(); const lang = useLang();
const [isHeaderHidden, setHeaderHidden] = useState(true); const [isHeaderHidden, setHeaderHidden] = useState(true);
const [currentSection, setCurrentSection] = useState<string | undefined>(initialSection); const [currentSection, setCurrentSection] = useState<string | undefined>(initialSection);
const [selectedSubscriptionOption, setSubscriptionOption] = useState<ApiPremiumSubscriptionOption>();
const handleOpen = useCallback((section: string | undefined) => { const handleOpen = useCallback((section: string | undefined) => {
return () => { return () => {
@ -146,7 +148,7 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
setHeaderHidden(scrollTop <= 150); setHeaderHidden(scrollTop <= 150);
} }
function handleClickWithStartParam(startParam?: string) { const handleClickWithStartParam = useLastCallback((startParam?: string) => {
const dialog = dialogRef.current; const dialog = dialogRef.current;
if (!dialog) return; if (!dialog) return;
@ -160,12 +162,21 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
}); });
closePremiumModal(); closePremiumModal();
} }
}
const handleClick = useLastCallback(() => {
handleClickWithStartParam();
}); });
const handleClick = useCallback(() => {
if (selectedSubscriptionOption) {
handleClickWithStartParam(String(selectedSubscriptionOption.months));
} else {
handleClickWithStartParam();
}
}, [selectedSubscriptionOption, handleClickWithStartParam]);
const handleChangeSubscriptionOption = useCallback((months: number) => {
const foundOption = promo?.options.find((option) => option.months === months);
setSubscriptionOption(foundOption);
}, [promo]);
const showConfetti = useCallback(() => { const showConfetti = useCallback(() => {
const dialog = dialogRef.current; const dialog = dialogRef.current;
if (!dialog) return; if (!dialog) return;
@ -206,6 +217,11 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
}); });
}, [loadStickers, fromUserStatusEmoji, fromUserStatusSet]); }, [loadStickers, fromUserStatusEmoji, fromUserStatusSet]);
useEffect(() => {
const [defaultOption] = promo?.options ?? [];
setSubscriptionOption(defaultOption);
}, [promo]);
const handleOpenStatusSet = useLastCallback(() => { const handleOpenStatusSet = useLastCallback(() => {
if (!fromUserStatusSet) return; if (!fromUserStatusSet) return;
@ -231,10 +247,15 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
return [renderText(first), link, renderText(second)]; return [renderText(first), link, renderText(second)];
}, [fromUser, fromUserStatusSet, lang]); }, [fromUser, fromUserStatusSet, lang]);
if (!promo || (fromUserStatusEmoji && !fromUserStatusSet)) return undefined; const fullMonthlyAmount = useMemo(() => {
const monthOption = promo?.options.find((option) => option.months === 1);
if (!monthOption) {
return undefined;
}
return Number(monthOption.amount);
}, [promo]);
// TODO Support all subscription options if (!promo || (fromUserStatusEmoji && !fromUserStatusSet)) return undefined;
const month = promo.options.find((option) => option.months === 1)!;
function getHeaderText() { function getHeaderText() {
if (isGift) { if (isGift) {
@ -279,6 +300,24 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
); );
} }
function renderSubscriptionOptions() {
return (
<div className={styles.subscriptionOptions}>
{promo?.options
.map((option) => (
<PremiumSubscriptionOption
className={styles.subscriptionOption}
key={option.amount}
option={option}
onChange={handleChangeSubscriptionOption}
fullMonthlyAmount={fullMonthlyAmount}
checked={selectedSubscriptionOption?.months === option.months}
/>
))}
</div>
);
}
return ( return (
<Modal <Modal
className={styles.root} className={styles.root}
@ -319,12 +358,12 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
<div className={styles.description}> <div className={styles.description}>
{renderText(getHeaderDescription(), ['simple_markdown', 'emoji'])} {renderText(getHeaderDescription(), ['simple_markdown', 'emoji'])}
</div> </div>
{!isPremium && renderSubscriptionOptions()}
<div className={buildClassName(styles.header, isHeaderHidden && styles.hiddenHeader)}> <div className={buildClassName(styles.header, isHeaderHidden && styles.hiddenHeader)}>
<h2 className={styles.premiumHeaderText}> <h2 className={styles.premiumHeaderText}>
{lang('TelegramPremium')} {lang('TelegramPremium')}
</h2> </h2>
</div> </div>
<div className={buildClassName(styles.list, isPremium && styles.noButton)}> <div className={buildClassName(styles.list, isPremium && styles.noButton)}>
{filteredSections.map((section, index) => { {filteredSections.map((section, index) => {
return ( return (
@ -355,10 +394,15 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
</div> </div>
{renderFooterText()} {renderFooterText()}
</div> </div>
{!isPremium && ( {!isPremium && selectedSubscriptionOption && (
<div className={styles.footer}> <div className={styles.footer}>
<Button className={styles.button} isShiny withPremiumGradient onClick={handleClick}> <Button className={styles.button} isShiny withPremiumGradient onClick={handleClick}>
{lang('SubscribeToPremium', formatCurrency(Number(month.amount), month.currency, lang.code))} {lang('SubscribeToPremium',
formatCurrency(
selectedSubscriptionOption.amount,
selectedSubscriptionOption.currency,
lang.code,
))}
</Button> </Button>
</div> </div>
)} )}

View File

@ -2,24 +2,27 @@ import type { ChangeEvent } from 'react';
import type { FC } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact';
import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact'; import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact';
import type { ApiPremiumGiftOption } from '../../../api/types';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { formatCurrency } from '../../../util/formatCurrency'; import { formatCurrency } from '../../../util/formatCurrency';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import styles from './GiftOption.module.scss'; import styles from './PremiumSubscriptionOption.module.scss';
type OwnProps = { type OwnProps = {
option: ApiPremiumGiftOption; option: {
months: number;
currency: string;
amount: number;
};
checked?: boolean; checked?: boolean;
fullMonthlyAmount?: number; fullMonthlyAmount?: number;
className?: string;
onChange: (month: number) => void; onChange: (month: number) => void;
}; };
const GiftOption: FC<OwnProps> = ({ const PremiumSubscriptionOption: FC<OwnProps> = ({
option, checked, fullMonthlyAmount, onChange, option, checked, fullMonthlyAmount, onChange, className,
}) => { }) => {
const lang = useLang(); const lang = useLang();
@ -39,7 +42,14 @@ const GiftOption: FC<OwnProps> = ({
}, [months, onChange]); }, [months, onChange]);
return ( return (
<label className={buildClassName(styles.wrapper, checked && styles.active)} dir={lang.isRtl ? 'rtl' : undefined}> <label
className={buildClassName(
styles.wrapper,
checked && styles.active,
className,
)}
dir={lang.isRtl ? 'rtl' : undefined}
>
<input <input
className={styles.input} className={styles.input}
type="radio" type="radio"
@ -62,4 +72,4 @@ const GiftOption: FC<OwnProps> = ({
); );
}; };
export default memo(GiftOption); export default memo(PremiumSubscriptionOption);

View File

@ -407,7 +407,7 @@ addActionHandler('openGiftPremiumModal', async (global, actions, payload): Promi
isOpen: true, isOpen: true,
forUserId, forUserId,
monthlyCurrency: month.currency, monthlyCurrency: month.currency,
monthlyAmount: month.amount, monthlyAmount: String(month.amount),
}, },
}, tabId); }, tabId);
setGlobal(global); setGlobal(global);