From 0ed8bb1d48918d7a441043b035c8cd0721f6c515 Mon Sep 17 00:00:00 2001 From: c0mrade Date: Fri, 29 May 2026 11:36:22 +0300 Subject: [PATCH] fix(navigation): single-tariff /subscriptions/:id deep-link shows Close, not Back MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refines 3c2f650 — that commit broke the loop by suppressing the single-tariff redirect on idx=0, but left the BackButton visible. The user still saw Back on the detail page and needed two taps (Back → Close) to exit. Серёжа's report was specifically that Back should not appear at all when you land on your only subscription via a bot button. Move the decision up to TelegramBackButton: - Subscribe to the existing ['subscriptions-list'] query (deduped by React Query, so no extra fetch). - On /subscriptions/:id, when multi_tariff_enabled is false and there is no in-app history (idx=0 deep-link entry), hide the BackButton. Telegram then surfaces its native Close (X) in the header. - Multi-tariff users still see BackButton on /subscriptions/:id deep-links because their /subscriptions list is meaningful (they have many subs). The 2bcba3b fallback-to-parent behaviour remains intact for unrelated deep-links like /balance/top-up, /admin/*, etc. Revert the defensive guard in Subscriptions.tsx — the loop can no longer be triggered because no BackButton is shown to trigger it. Behaviour summary: flow BackButton exit cost -------------------------------------- ---------- --------- deep-link → /subscriptions/:id (1T) hidden Close, 1 tap / → /subscriptions → /subscriptions/:id shown Back → /, 1 tap deep-link → /balance/top-up shown Back → /balance ✓ deep-link → /subscriptions/:id (multi) shown Back → /subscriptions list Reported by Серёжа, bugs topic #599679. --- src/AppWithNavigator.tsx | 27 +++++++++++++++++++++++++-- src/pages/Subscriptions.tsx | 8 ++------ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/AppWithNavigator.tsx b/src/AppWithNavigator.tsx index f0f20cc..d43e4b8 100644 --- a/src/AppWithNavigator.tsx +++ b/src/AppWithNavigator.tsx @@ -6,6 +6,7 @@ import { onBackButtonClick, offBackButtonClick, } from '@telegram-apps/sdk-react'; +import { useQuery } from '@tanstack/react-query'; import Twemoji from 'react-twemoji'; import App from './App'; import { ErrorBoundary } from './components/ErrorBoundary'; @@ -16,6 +17,7 @@ import { ToastProvider } from './components/Toast'; import { TooltipProvider } from './components/primitives/Tooltip'; import { isInTelegramWebApp } from './hooks/useTelegramSDK'; import { hasInAppHistory, getFallbackParentPath } from './utils/navigation'; +import { subscriptionApi } from './api/subscription'; const TWEMOJI_OPTIONS = { className: 'twemoji', folder: 'svg', ext: '.svg' } as const; @@ -26,6 +28,13 @@ const TWEMOJI_OPTIONS = { className: 'twemoji', folder: 'svg', ext: '.svg' } as /** Pages reachable from bottom nav — treat as top-level (no back button). */ const BOTTOM_NAV_PATHS = ['/', '/subscriptions', '/balance', '/referral', '/support', '/wheel']; +/** Matches /subscriptions/:numericId — single-tariff users land here from + * bot deep-links, but their /subscriptions list is empty (the list view + * auto-redirects them straight back to this page). Pressing Back would loop + * back to detail, so on a deep-link entry (idx=0) we hide the back button + * and let Telegram surface its native Close (X) button instead. */ +const SUBSCRIPTION_DETAIL_RE = /^\/subscriptions\/\d+\/?$/; + function TelegramBackButton() { const location = useLocation(); const navigate = useNavigate(); @@ -34,16 +43,30 @@ function TelegramBackButton() { const pathnameRef = useRef(location.pathname); pathnameRef.current = location.pathname; + // Share the subscriptions-list query with the page-level components. + // React Query dedupes by key so this does not cause an extra fetch when + // Subscriptions/Subscription/Dashboard pages mount. + const { data: subData } = useQuery({ + queryKey: ['subscriptions-list'], + queryFn: () => subscriptionApi.getSubscriptions(), + staleTime: 30_000, + // Don't fetch outside Telegram — the cabinet still loads on the web. + enabled: isInTelegramWebApp(), + }); + const isMultiTariff = subData?.multi_tariff_enabled ?? false; + useEffect(() => { const isTopLevel = location.pathname === '' || BOTTOM_NAV_PATHS.includes(location.pathname); + const isSingleTariffDetailDeepLink = + !isMultiTariff && SUBSCRIPTION_DETAIL_RE.test(location.pathname) && !hasInAppHistory(); try { - if (isTopLevel) { + if (isTopLevel || isSingleTariffDetailDeepLink) { hideBackButton(); } else { showBackButton(); } } catch {} - }, [location]); + }, [location, isMultiTariff]); // Stable handler — ref prevents re-subscription on every render const handler = useCallback(() => { diff --git a/src/pages/Subscriptions.tsx b/src/pages/Subscriptions.tsx index 0b9a0bd..9fb032f 100644 --- a/src/pages/Subscriptions.tsx +++ b/src/pages/Subscriptions.tsx @@ -9,7 +9,6 @@ import { getGlassColors } from '../utils/glassTheme'; import { useAuthStore } from '../store/auth'; import SubscriptionListCard from '../components/subscription/SubscriptionListCard'; import TrialOfferCard from '../components/dashboard/TrialOfferCard'; -import { hasInAppHistory } from '../utils/navigation'; function EmptyState({ onBuy }: { onBuy: () => void }) { const { t } = useTranslation(); @@ -107,11 +106,8 @@ export default function Subscriptions() { }, }); - // Single-tariff mode with one subscription: skip list, go directly to detail. - // Skip the redirect when the user just hit the Telegram BackButton from - // /subscriptions/:id — that lands us here with idx=0, and redirecting back - // to the detail page creates a Back-button loop the user can't escape (#599679). - if (data && !isMultiTariff && subscriptions.length === 1 && hasInAppHistory()) { + // Single-tariff mode with one subscription: skip list, go directly to detail + if (data && !isMultiTariff && subscriptions.length === 1) { return ; }