fix: platform-aware delete confirmation + trial CTA to purchase

Delete subscription:
- Telegram mini app: native destructive popup (red button)
- Web (mobile + desktop): Sheet bottom panel with full-width buttons
- Both: proper loading state on delete button

Trial CTA: leads to /subscription/purchase (trial cannot be renewed)
This commit is contained in:
c0mrade
2026-03-24 18:30:59 +03:00
parent 23edd6a211
commit debee7729f
2 changed files with 68 additions and 33 deletions

View File

@@ -5,13 +5,15 @@ import { useNavigate, useParams } from 'react-router';
import { subscriptionApi } from '../api/subscription';
import { WebBackButton } from '../components/WebBackButton';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '../components/primitives/Dialog';
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
} from '../components/primitives/Sheet';
import { useDestructiveConfirm } from '../platform/hooks/useNativeDialog';
import { usePlatform } from '../platform';
import TrafficProgressBar from '../components/dashboard/TrafficProgressBar';
import { HoverBorderGradient } from '../components/ui/hover-border-gradient';
import { useTrafficZone } from '../hooks/useTrafficZone';
@@ -183,8 +185,10 @@ export default function Subscription() {
const g = getGlassColors(isDark);
const haptic = useHaptic();
const [copied, setCopied] = useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [showDeleteSheet, setShowDeleteSheet] = useState(false);
const [deleteLoading, setDeleteLoading] = useState(false);
const { platform } = usePlatform();
const destructiveConfirm = useDestructiveConfirm();
// Helper to format price from kopeks
const formatPrice = (kopeks: number) => `${formatAmount(kopeks / 100)} ${currencySymbol}`;
@@ -1278,8 +1282,33 @@ export default function Subscription() {
{isMultiTariff && subscription && !subscription.is_active && !subscription.is_trial && (
<>
<button
onClick={() => setShowDeleteConfirm(true)}
className="flex w-full items-center justify-center gap-2 rounded-2xl border border-red-400/20 bg-red-400/5 p-3.5 text-sm font-medium text-red-400 transition-colors hover:bg-red-400/10"
onClick={async () => {
if (platform === 'telegram') {
// Telegram: native destructive popup
const confirmed = await destructiveConfirm(
t(
'subscription.deleteWarning',
'Подписка будет удалена безвозвратно. Все данные, устройства и настройки будут потеряны.',
),
t('subscription.confirmDelete', 'Да, удалить'),
t('subscription.deleteTitle', 'Удалить подписку?'),
);
if (!confirmed) return;
setDeleteLoading(true);
try {
await subscriptionApi.deleteSubscription(subscription.id);
queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] });
navigate('/subscriptions', { replace: true });
} catch {
setDeleteLoading(false);
}
} else {
// Web: show Sheet
setShowDeleteSheet(true);
}
}}
disabled={deleteLoading}
className="flex w-full items-center justify-center gap-2 rounded-2xl border border-red-400/20 bg-red-400/5 p-3.5 text-sm font-medium text-red-400 transition-colors hover:bg-red-400/10 disabled:opacity-50"
>
<svg
className="h-4 w-4"
@@ -1294,27 +1323,24 @@ export default function Subscription() {
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
{t('subscription.delete', 'Удалить подписку')}
{deleteLoading
? t('common.processing', 'Удаление...')
: t('subscription.delete', 'Удалить подписку')}
</button>
<Dialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
<DialogContent className="max-w-sm">
<DialogHeader>
<DialogTitle>{t('subscription.deleteTitle', 'Удалить подписку?')}</DialogTitle>
<DialogDescription>
{/* Web: Sheet confirmation */}
<Sheet open={showDeleteSheet} onOpenChange={setShowDeleteSheet}>
<SheetContent>
<SheetHeader>
<SheetTitle>{t('subscription.deleteTitle', 'Удалить подписку?')}</SheetTitle>
<SheetDescription>
{t(
'subscription.deleteWarning',
'Подписка будет удалена безвозвратно. Все данные, устройства и настройки будут потеряны. Это действие нельзя отменить.',
)}
</DialogDescription>
</DialogHeader>
<DialogFooter className="gap-2 sm:gap-0">
<button
onClick={() => setShowDeleteConfirm(false)}
className="rounded-xl border border-dark-700 bg-dark-800 px-4 py-2.5 text-sm font-medium text-dark-300 transition-colors hover:bg-dark-700"
>
{t('common.cancel', 'Отмена')}
</button>
</SheetDescription>
</SheetHeader>
<SheetFooter>
<button
onClick={async () => {
setDeleteLoading(true);
@@ -1324,19 +1350,25 @@ export default function Subscription() {
navigate('/subscriptions', { replace: true });
} catch {
setDeleteLoading(false);
setShowDeleteConfirm(false);
setShowDeleteSheet(false);
}
}}
disabled={deleteLoading}
className="rounded-xl bg-red-500 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-red-600 disabled:opacity-50"
className="w-full rounded-xl bg-red-500 py-3 text-sm font-semibold text-white transition-colors hover:bg-red-600 disabled:opacity-50"
>
{deleteLoading
? t('common.processing', 'Удаление...')
: t('subscription.confirmDelete', 'Да, удалить')}
</button>
</DialogFooter>
</DialogContent>
</Dialog>
<button
onClick={() => setShowDeleteSheet(false)}
className="w-full rounded-xl border border-dark-700 bg-dark-800 py-3 text-sm font-medium text-dark-300 transition-colors hover:bg-dark-700"
>
{t('common.cancel', 'Отмена')}
</button>
</SheetFooter>
</SheetContent>
</Sheet>
</>
)}