feat(navigation): роутинг startapp-диплинков rich-меню бота

StartParamNavigator обобщён до таблицы маршрутов: помимо admin_ticket_<id>
теперь разбираются renew_<id> → /subscriptions/<id>/renew и subscriptions →
/subscriptions — их шлют ссылки «Продлить» у истёкших подписок в rich-меню
бота (t.me/<bot>/<app>?startapp=…). Доступ по-прежнему ограничен гвардами
самих маршрутов.

biome check, type-check и build проходят (pre-commit пропущен: biome не
установлен локально как зависимость — бинарь есть только в CI).
This commit is contained in:
Fringg
2026-07-13 02:12:57 +03:00
parent 758a645dff
commit 2eaea75fd9

View File

@@ -173,18 +173,24 @@ function TelegramBackButton() {
return null;
}
/** `admin_ticket_<id>` startapp param → /admin/tickets/<id>. */
const ADMIN_TICKET_START_PARAM_RE = /^admin_ticket_(\d+)$/;
/** Supported startapp paramsin-app destinations. */
const START_PARAM_ROUTES: Array<{ re: RegExp; to: (match: RegExpExecArray) => string }> = [
// Admin ticket notification buttons in group chats (bot issue #2988).
{ re: /^admin_ticket_(\d+)$/, to: (match) => `/admin/tickets/${match[1]}` },
// «Продлить» links for expired subscriptions in the bot's rich main menu.
{ re: /^renew_(\d+)$/, to: (match) => `/subscriptions/${match[1]}/renew` },
{ re: /^subscriptions$/, to: () => '/subscriptions' },
];
/**
* Routes a Telegram Mini App start param to an in-app destination on launch.
*
* Admin ticket notification buttons in GROUP/channel chats open the cabinet via
* a `t.me/<bot>/<app>?startapp=admin_ticket_<id>` deep link (bot issue #2988) —
* `web_app` buttons don't work in group chats, so the startapp param is the only
* way in. Telegram delivers it as `tgWebAppStartParam`; we map it to the admin
* ticket route once on mount. Access is still gated by the route's
* `PermissionRoute permission="tickets:read"`.
* Text links and buttons outside private-chat web_app buttons can only enter the
* Mini App via `t.me/<bot>/<app>?startapp=<param>` deep links: admin ticket
* notifications in GROUP/channel chats (bot issue #2988) and the bot's rich
* main-menu «Продлить» links for expired subscriptions. Telegram delivers the
* param as `tgWebAppStartParam`; we map it to a route once on mount. Access is
* still gated by each route's own guards (e.g. `PermissionRoute`).
*/
function StartParamNavigator() {
const navigate = useNavigate();
@@ -202,9 +208,12 @@ function StartParamNavigator() {
}
if (!startParam) return;
const match = ADMIN_TICKET_START_PARAM_RE.exec(startParam);
for (const { re, to } of START_PARAM_ROUTES) {
const match = re.exec(startParam);
if (match) {
navigate(`/admin/tickets/${match[1]}`, { replace: true });
navigate(to(match), { replace: true });
return;
}
}
}, [navigate]);