feat: renewal btn

This commit is contained in:
2026-08-17 21:34:44 +07:00
parent 4e788312ab
commit f2940e55bd
5 changed files with 124 additions and 9 deletions

View File

@@ -12,6 +12,8 @@ import { DeviceCard } from '../components/DeviceCard';
import { Modal } from '../components/Modal';
import { SubscriptionQrModal } from '../components/SubscriptionQrModal';
import { ClientLinksModal } from '../components/ClientLinksModal';
import { formatMoney } from '../lib/format';
import { redirectTo } from '../lib/redirect';
import type { Device, SubscriptionData, UserInfo } from '../types';
export function AccountPage() {
@@ -27,6 +29,8 @@ export function AccountPage() {
const [isQrOpen, setIsQrOpen] = useState(false);
const [isReferralQrOpen, setIsReferralQrOpen] = useState(false);
const [isClientLinksOpen, setIsClientLinksOpen] = useState(false);
const [isRenewalOpen, setIsRenewalOpen] = useState(false);
const [isRenewing, setIsRenewing] = useState(false);
useEffect(() => {
if (!isAuthenticated) {
@@ -98,18 +102,60 @@ export function AccountPage() {
navigate('/', { replace: true });
};
const openPlanWithCurrentConfig = () => {
if (!subscription) return;
const params = new URLSearchParams();
if (subscription.devices) params.set('devices', String(subscription.devices));
if (subscription.duration_days) params.set('duration', String(subscription.duration_days));
if (subscription.addon_ids.length > 0) params.set('addons', subscription.addon_ids.join(','));
navigate(`/plan?${params.toString()}`);
};
const renewCurrentPlan = async () => {
if (!subscription?.devices || !subscription.duration_days) {
openPlanWithCurrentConfig();
return;
}
setIsRenewing(true);
try {
const result = await api.checkout({
devices: subscription.devices,
addons: subscription.addon_ids,
duration_days: subscription.duration_days,
});
if (result.payment_link) {
redirectTo(result.payment_link);
return;
}
setIsRenewalOpen(false);
showToast(`Заказ ${result.order_id} оформлен. К оплате: ${formatMoney(result.amount_to_pay)}`);
} catch (err: unknown) {
const message = err instanceof ApiError ? err.message : 'Не удалось оформить продление';
showToast(message);
} finally {
setIsRenewing(false);
}
};
return (
<Shell active="account">
<section className="account-hero">
<Reveal>
<div className="kicker">Ваш аккаунт</div>
<h1 className="hero-title">{userInfo?.username ?? 'Аккаунт'}</h1>
{!loadingMeta ? (
<p className="hero-sub">
{subscription?.has_subscription && subscription.expires_at
? `Подписка до ${new Date(subscription.expires_at).toLocaleDateString('ru-RU')}`
: 'Активная подписка отсутствует'}
</p>
{!loadingMeta ? (
<>
<p className="hero-sub">
{subscription?.has_subscription && subscription.expires_at
? `Подписка до ${new Date(subscription.expires_at).toLocaleDateString('ru-RU')}`
: 'Активная подписка отсутствует'}
</p>
{subscription?.has_subscription ? (
<button type="button" className="btn btn-primary account-renew" onClick={() => setIsRenewalOpen(true)}>
Продлить
</button>
) : null}
</>
) : null}
</Reveal>
</section>
@@ -275,6 +321,19 @@ export function AccountPage() {
confirmLabel="Удалить"
onConfirm={confirmDelete}
/>
<Modal
open={isRenewalOpen}
onClose={() => setIsRenewalOpen(false)}
kicker="Продление"
title="Продлить с текущими параметрами?"
body="Мы повторим число устройств, дополнения и срок вашей текущей подписки."
confirmLabel={isRenewing ? 'Оформление…' : 'Да, продлить'}
cancelLabel="Нет, настроить"
confirmClassName="btn-primary"
pending={isRenewing}
onCancel={openPlanWithCurrentConfig}
onConfirm={() => void renewCurrentPlan()}
/>
<SubscriptionQrModal
link={isQrOpen ? subscription?.subscription_link ?? null : null}
onClose={() => setIsQrOpen(false)}