diff --git a/src/AppWithNavigator.tsx b/src/AppWithNavigator.tsx index c89d016..0aaeb50 100644 --- a/src/AppWithNavigator.tsx +++ b/src/AppWithNavigator.tsx @@ -15,9 +15,10 @@ import { ThemeColorsProvider } from './providers/ThemeColorsProvider'; import { WebSocketProvider } from './providers/WebSocketProvider'; import { ToastProvider } from './components/Toast'; import { TooltipProvider } from './components/primitives/Tooltip'; -import { isInTelegramWebApp } from './hooks/useTelegramSDK'; +import { isInTelegramWebApp, closeTelegramApp } from './hooks/useTelegramSDK'; import { getFallbackParentPath } from './utils/navigation'; import { subscriptionApi } from './api/subscription'; +import { useBlockingStore } from './store/blocking'; const TWEMOJI_OPTIONS = { className: 'twemoji', folder: 'svg', ext: '.svg' } as const; @@ -44,6 +45,13 @@ function TelegramBackButton() { const pathnameRef = useRef(location.pathname); pathnameRef.current = location.pathname; + // A full-screen blocking overlay (maintenance / channel-sub / blacklist / + // account-deleted / backend-unavailable) takes over the native back button: + // there is nowhere to navigate, so it becomes a single, stable EXIT control. + const blockingType = useBlockingStore((state) => state.blockingType); + const blockingTypeRef = useRef(blockingType); + blockingTypeRef.current = blockingType; + // Reliable in-app navigation depth (the app's entry point is 0). Driven by // React Router's navigation TYPE — NOT window.history.state.idx, which the // app's own redirects mutate unpredictably and which is the root flake behind @@ -89,6 +97,15 @@ function TelegramBackButton() { subsCountRef.current = subsCount; useEffect(() => { + // On a blocking overlay, keep exactly one visible Back button (its click + // exits the app — see handler). Skip the route logic so it can't flip + // between Back and Close as the hidden route changes underneath. + if (blockingType) { + try { + showBackButton(); + } catch {} + return; + } const isTopLevel = location.pathname === '' || BOTTOM_NAV_PATHS.includes(location.pathname); // Depth-independent on purpose: whether the user deep-linked in or navigated // here in-app, a single-tariff detail whose list just bounces back has no @@ -103,10 +120,17 @@ function TelegramBackButton() { showBackButton(); } } catch {} - }, [location, listRedirectsToDetail]); + }, [location, listRedirectsToDetail, blockingType]); // Stable handler — ref prevents re-subscription on every render const handler = useCallback(() => { + // A blocking overlay is a hard block with nowhere to navigate — the back + // button's only job is to EXIT the Mini App (no SPA navigation, so it can't + // flip-flop between Back and Close). + if (blockingTypeRef.current) { + closeTelegramApp(); + return; + } // Real in-app history (depth > 0): a normal back. Otherwise we were opened // directly on this route via a deep-link — navigate(-1) is a no-op, so fall // back to a sensible parent route instead. diff --git a/src/components/blocking/ServiceUnavailableScreen.tsx b/src/components/blocking/ServiceUnavailableScreen.tsx index cfcd64b..4cd2287 100644 --- a/src/components/blocking/ServiceUnavailableScreen.tsx +++ b/src/components/blocking/ServiceUnavailableScreen.tsx @@ -4,8 +4,7 @@ import { useQueryClient } from '@tanstack/react-query'; import { useBlockingStore } from '../../store/blocking'; import { useFocusTrap } from '../../hooks/useFocusTrap'; import { pingBackend, hasEverReachedBackend } from '../../api/health'; -import { isInTelegramWebApp, closeTelegramApp } from '../../hooks/useTelegramSDK'; -import { CloudWarningIcon, RestartIcon, CloseIcon } from '@/components/icons'; +import { CloudWarningIcon, RestartIcon } from '@/components/icons'; import { Button } from '@/components/primitives'; import { cn } from '@/lib/utils'; import BlockingShell from './BlockingShell'; @@ -25,7 +24,6 @@ export default function ServiceUnavailableScreen() { const queryClient = useQueryClient(); const [isChecking, setIsChecking] = useState(false); const isCheckingRef = useRef(false); - const inTelegram = isInTelegramWebApp(); const recover = useCallback(() => { clearBlocking(); @@ -87,33 +85,18 @@ export default function ServiceUnavailableScreen() { pulse footer={t('blocking.serviceUnavailable.hint')} actions={ - <> - - {/* Telegram Mini App only — a browser tab can't be closed by script. - Reliably exits the Mini App instead of routing back. */} - {inTelegram && ( - - )} - + } /> );