fix(navigation): single-tariff subscription detail всегда Close, не Back

Регрессия после a62d689: Back прятался только при deep-link входе
(depthRef === 0). При внутреннем переходе на /subscriptions/:id (depth > 0)
показывался Back, а в single-tariff «назад» уходил в /subscriptions, который
тут же редиректит обратно на деталь (Subscriptions.tsx) — петля, и юзер видел
Back вместо Close.

Решение depth-независимое: если список-редирект всё равно отбрасывает на эту
же страницу (single-tariff, ≤1 подписки = listRedirectsToDetail, инверсия
listIsSafe из обработчика), деталь — это верхний экран. Прячем Back всегда,
Telegram показывает нативный Close. Петля невозможна по построению: без кнопки
Back обработчик navigate(-1) в редиректящий список просто не вызвать.

Multi-tariff и >1 подписки сохраняют реальный Back к осмысленному списку.
This commit is contained in:
c0mrade
2026-06-01 20:29:03 +03:00
parent 484c3ad005
commit ec346d38d8

View File

@@ -28,11 +28,11 @@ 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 straight
* from bot deep-links, and their /subscriptions list auto-redirects right back
* to this page (Subscriptions.tsx). So on a genuine deep-link entry (in-app
* navigation depth 0) we hide the back button and let Telegram surface its
* native Close (X); when there IS in-app history we show it and navigate back. */
/** Matches /subscriptions/:numericId. When the user has a single tariff and at
* most one subscription, the /subscriptions list auto-redirects straight back
* here (Subscriptions.tsx), so this page is effectively top-level: we hide the
* back button and let Telegram surface its native Close (X). Multi-tariff users
* (or anyone with >1 subscription) keep a real Back to their meaningful list. */
const SUBSCRIPTION_DETAIL_RE = /^\/subscriptions\/\d+\/?$/;
function TelegramBackButton() {
@@ -71,27 +71,39 @@ function TelegramBackButton() {
enabled: isInTelegramWebApp(),
});
const isMultiTariff = subData?.multi_tariff_enabled ?? false;
const subsCount = subData?.subscriptions?.length ?? 0;
// The /subscriptions list silently redirects straight back to the open detail
// page when there is a single tariff and at most one subscription
// (Subscriptions.tsx). In that mode the detail page IS the top-level screen —
// there is no meaningful "back" target. Inverse of the handler's `listIsSafe`.
// Defaults to true on a cold cache (isMultiTariff=false, subsCount=0): hiding
// Back is fail-closed — far better than briefly arming the looping Back.
const listRedirectsToDetail = !isMultiTariff && subsCount <= 1;
// Refs so the stable back handler (memoised with []) reads fresh values
// without re-subscribing — re-subscription lets a component's local handler
// overwrite ours via Telegram's singleton onBackButtonClick (issue #436).
const isMultiTariffRef = useRef(isMultiTariff);
isMultiTariffRef.current = isMultiTariff;
const subsCountRef = useRef(subData?.subscriptions?.length ?? 0);
subsCountRef.current = subData?.subscriptions?.length ?? 0;
const subsCountRef = useRef(subsCount);
subsCountRef.current = subsCount;
useEffect(() => {
const isTopLevel = location.pathname === '' || BOTTOM_NAV_PATHS.includes(location.pathname);
const isSingleTariffDetailDeepLink =
!isMultiTariff && SUBSCRIPTION_DETAIL_RE.test(location.pathname) && depthRef.current === 0;
// 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
// real "back" target. Showing Back here is exactly what looped through the
// redirecting /subscriptions list (#436); hiding it always surfaces Close.
const isRedirectingSubscriptionDetail =
listRedirectsToDetail && SUBSCRIPTION_DETAIL_RE.test(location.pathname);
try {
if (isTopLevel || isSingleTariffDetailDeepLink) {
if (isTopLevel || isRedirectingSubscriptionDetail) {
hideBackButton();
} else {
showBackButton();
}
} catch {}
}, [location, isMultiTariff]);
}, [location, listRedirectsToDetail]);
// Stable handler — ref prevents re-subscription on every render
const handler = useCallback(() => {