mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
fix(cabinet): blocking screens exit via the native Telegram back button, drop the in-page Close
On a full-screen blocking overlay in the Telegram Mini App there were two exit affordances: the native back button — which ran SPA navigation on the hidden route underneath, so it didn't dismiss the block and kept flip-flopping between Back and Close as the route changed — and a redundant in-page "Close" button on the service-unavailable screen. Make the native back button the single, stable exit: while any blocking screen is active, TelegramBackButton shows one Back button whose click closes the Mini App (closeTelegramApp) instead of navigating the SPA — no route change, no Back/Close flip-flop. The normal route-based back button is restored once the block clears. Remove the in-page Close button.
This commit is contained in:
@@ -15,9 +15,10 @@ import { ThemeColorsProvider } from './providers/ThemeColorsProvider';
|
|||||||
import { WebSocketProvider } from './providers/WebSocketProvider';
|
import { WebSocketProvider } from './providers/WebSocketProvider';
|
||||||
import { ToastProvider } from './components/Toast';
|
import { ToastProvider } from './components/Toast';
|
||||||
import { TooltipProvider } from './components/primitives/Tooltip';
|
import { TooltipProvider } from './components/primitives/Tooltip';
|
||||||
import { isInTelegramWebApp } from './hooks/useTelegramSDK';
|
import { isInTelegramWebApp, closeTelegramApp } from './hooks/useTelegramSDK';
|
||||||
import { getFallbackParentPath } from './utils/navigation';
|
import { getFallbackParentPath } from './utils/navigation';
|
||||||
import { subscriptionApi } from './api/subscription';
|
import { subscriptionApi } from './api/subscription';
|
||||||
|
import { useBlockingStore } from './store/blocking';
|
||||||
|
|
||||||
const TWEMOJI_OPTIONS = { className: 'twemoji', folder: 'svg', ext: '.svg' } as const;
|
const TWEMOJI_OPTIONS = { className: 'twemoji', folder: 'svg', ext: '.svg' } as const;
|
||||||
|
|
||||||
@@ -44,6 +45,13 @@ function TelegramBackButton() {
|
|||||||
const pathnameRef = useRef(location.pathname);
|
const pathnameRef = useRef(location.pathname);
|
||||||
pathnameRef.current = 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
|
// 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
|
// 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
|
// app's own redirects mutate unpredictably and which is the root flake behind
|
||||||
@@ -89,6 +97,15 @@ function TelegramBackButton() {
|
|||||||
subsCountRef.current = subsCount;
|
subsCountRef.current = subsCount;
|
||||||
|
|
||||||
useEffect(() => {
|
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);
|
const isTopLevel = location.pathname === '' || BOTTOM_NAV_PATHS.includes(location.pathname);
|
||||||
// Depth-independent on purpose: whether the user deep-linked in or navigated
|
// 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
|
// here in-app, a single-tariff detail whose list just bounces back has no
|
||||||
@@ -103,10 +120,17 @@ function TelegramBackButton() {
|
|||||||
showBackButton();
|
showBackButton();
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
}, [location, listRedirectsToDetail]);
|
}, [location, listRedirectsToDetail, blockingType]);
|
||||||
|
|
||||||
// Stable handler — ref prevents re-subscription on every render
|
// Stable handler — ref prevents re-subscription on every render
|
||||||
const handler = useCallback(() => {
|
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
|
// 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
|
// directly on this route via a deep-link — navigate(-1) is a no-op, so fall
|
||||||
// back to a sensible parent route instead.
|
// back to a sensible parent route instead.
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import { useQueryClient } from '@tanstack/react-query';
|
|||||||
import { useBlockingStore } from '../../store/blocking';
|
import { useBlockingStore } from '../../store/blocking';
|
||||||
import { useFocusTrap } from '../../hooks/useFocusTrap';
|
import { useFocusTrap } from '../../hooks/useFocusTrap';
|
||||||
import { pingBackend, hasEverReachedBackend } from '../../api/health';
|
import { pingBackend, hasEverReachedBackend } from '../../api/health';
|
||||||
import { isInTelegramWebApp, closeTelegramApp } from '../../hooks/useTelegramSDK';
|
import { CloudWarningIcon, RestartIcon } from '@/components/icons';
|
||||||
import { CloudWarningIcon, RestartIcon, CloseIcon } from '@/components/icons';
|
|
||||||
import { Button } from '@/components/primitives';
|
import { Button } from '@/components/primitives';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import BlockingShell from './BlockingShell';
|
import BlockingShell from './BlockingShell';
|
||||||
@@ -25,7 +24,6 @@ export default function ServiceUnavailableScreen() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [isChecking, setIsChecking] = useState(false);
|
const [isChecking, setIsChecking] = useState(false);
|
||||||
const isCheckingRef = useRef(false);
|
const isCheckingRef = useRef(false);
|
||||||
const inTelegram = isInTelegramWebApp();
|
|
||||||
|
|
||||||
const recover = useCallback(() => {
|
const recover = useCallback(() => {
|
||||||
clearBlocking();
|
clearBlocking();
|
||||||
@@ -87,33 +85,18 @@ export default function ServiceUnavailableScreen() {
|
|||||||
pulse
|
pulse
|
||||||
footer={t('blocking.serviceUnavailable.hint')}
|
footer={t('blocking.serviceUnavailable.hint')}
|
||||||
actions={
|
actions={
|
||||||
<>
|
<Button
|
||||||
<Button
|
variant="secondary"
|
||||||
variant="secondary"
|
size="lg"
|
||||||
size="lg"
|
fullWidth
|
||||||
fullWidth
|
onClick={handleRetry}
|
||||||
onClick={handleRetry}
|
disabled={isChecking}
|
||||||
disabled={isChecking}
|
leftIcon={<RestartIcon className={cn('h-5 w-5', isChecking && 'animate-spin')} />}
|
||||||
leftIcon={<RestartIcon className={cn('h-5 w-5', isChecking && 'animate-spin')} />}
|
>
|
||||||
>
|
{isChecking
|
||||||
{isChecking
|
? t('blocking.serviceUnavailable.checking')
|
||||||
? t('blocking.serviceUnavailable.checking')
|
: t('blocking.serviceUnavailable.retry')}
|
||||||
: t('blocking.serviceUnavailable.retry')}
|
</Button>
|
||||||
</Button>
|
|
||||||
{/* Telegram Mini App only — a browser tab can't be closed by script.
|
|
||||||
Reliably exits the Mini App instead of routing back. */}
|
|
||||||
{inTelegram && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="lg"
|
|
||||||
fullWidth
|
|
||||||
onClick={closeTelegramApp}
|
|
||||||
leftIcon={<CloseIcon className="h-5 w-5" />}
|
|
||||||
>
|
|
||||||
{t('blocking.serviceUnavailable.close')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user