From 29b6e6eb24f6fb9b4be8067dd59ac92a9ffb9da4 Mon Sep 17 00:00:00 2001 From: 3252a8 <3252a8@proton.me> Date: Sun, 24 May 2026 09:32:47 +0300 Subject: [PATCH] fix: keep payment user cards in payments --- backend/bot/app/web/webapp/routes.py | 1 + frontend/src/App.svelte | 2 ++ frontend/src/admin/AdminPanel.svelte | 31 +++++++++++++++++---- frontend/src/lib/admin/stores/usersStore.js | 25 +++++++++++++++-- frontend/src/lib/webapp/routes.js | 12 ++++++++ 5 files changed, 64 insertions(+), 7 deletions(-) diff --git a/backend/bot/app/web/webapp/routes.py b/backend/bot/app/web/webapp/routes.py index c5b9716..f6e171d 100644 --- a/backend/bot/app/web/webapp/routes.py +++ b/backend/bot/app/web/webapp/routes.py @@ -23,6 +23,7 @@ def setup_subscription_webapp_routes(app: web.Application) -> None: index_route, ) app.router.add_get("/admin/users/{user_id:-?[0-9]+}", index_route) + app.router.add_get("/admin/payments/users/{user_id:-?[0-9]+}", index_route) app.router.add_get("/admin/payments/{payment_id:\\d+}", index_route) app.router.add_get("/admin/support/{ticket_id:\\d+}", index_route) app.router.add_get("/auth/telegram/start", telegram_oauth_start_route) diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 63b5a3b..fc60c71 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -80,6 +80,7 @@ import { DEV_MOCK, applyPreviewMock } from "./lib/webapp/previewMock.js"; import { adminPaymentIdFromPath, + adminPaymentsUserIdFromPath, adminSectionFromPath, adminUserIdFromPath, normalizeSection, @@ -790,6 +791,7 @@ onToast: (text) => showToast(text), initialSection: adminSectionFromPath(window.location.pathname), initialPaymentId: adminPaymentIdFromPath(window.location.pathname), + initialPaymentUserId: adminPaymentsUserIdFromPath(window.location.pathname), initialUserId: adminUserIdFromPath(window.location.pathname), onSectionChange: handleAdminSectionChange, onSettingsSaved: handleAdminPersistedSaved, diff --git a/frontend/src/admin/AdminPanel.svelte b/frontend/src/admin/AdminPanel.svelte index a0c8e47..6b54b0b 100644 --- a/frontend/src/admin/AdminPanel.svelte +++ b/frontend/src/admin/AdminPanel.svelte @@ -77,6 +77,7 @@ export let onToast = () => {}; export let initialSection = "stats"; export let initialPaymentId = null; + export let initialPaymentUserId = null; export let initialUserId = null; export let onSectionChange = () => {}; export let onSettingsSaved = () => {}; @@ -283,13 +284,24 @@ return match ? Number(match[1]) : null; } + function readPaymentUserIdFromPath() { + if (typeof window === "undefined") return null; + const match = window.location.pathname.match(/^\/admin\/payments\/users\/(-?\d+)$/); + return match ? Number(match[1]) : null; + } + function onPopState() { active = readSectionFromPath(); sidebarOpen = false; const userId = readUserIdFromPath(); - if (userId) { - if (!$usersStore.openedUser || $usersStore.openedUser.user_id !== userId) { - usersStore.openUser(userId, { skipPush: true }); + const paymentUserId = active === "payments" ? readPaymentUserIdFromPath() : null; + const contextualUserId = paymentUserId || userId; + if (contextualUserId) { + if (!$usersStore.openedUser || $usersStore.openedUser.user_id !== contextualUserId) { + usersStore.openUser(contextualUserId, { + skipPush: true, + pathContext: paymentUserId ? "payments" : "users", + }); } } else if ($usersStore.openedUser) { usersStore.closeUser({ skipPush: true }); @@ -321,14 +333,16 @@ const uid = Number(userId); // Synthetic email-only users use negative user_id; still a valid admin target. if (!Number.isFinite(uid) || uid === 0) return; - const next = normalizeSection("users"); + const next = normalizeSection("payments"); sidebarOpen = false; if (active !== next) { active = next; usersStore.closeUser(); + paymentsStore.closePayment({ skipPush: true }); onSectionChange(next); } - usersStore.openUser(uid); + paymentsStore.closePayment({ skipPush: true }); + usersStore.openUser(uid, { pathContext: "payments" }); } function resolvedAvatarUrl(user) { @@ -460,6 +474,13 @@ paymentsStore.openPayment(initialPaymentId, { skipPush: true }); } + $: if ( + active === "payments" && + initialPaymentUserId && + (!$usersStore.openedUser || $usersStore.openedUser.user_id !== initialPaymentUserId) + ) { + usersStore.openUser(initialPaymentUserId, { skipPush: true, pathContext: "payments" }); + }
diff --git a/frontend/src/lib/admin/stores/usersStore.js b/frontend/src/lib/admin/stores/usersStore.js index 329f0e3..ae4d89b 100644 --- a/frontend/src/lib/admin/stores/usersStore.js +++ b/frontend/src/lib/admin/stores/usersStore.js @@ -42,16 +42,34 @@ export function createUsersStore({ api, onToast, at }) { }); let _activeRef = "stats"; // fallback if active isn't tracked + let _pathContext = null; function setActive(active) { _activeRef = active; } + function _setPathContext(context) { + if (context === "payments") { + _pathContext = "payments"; + return; + } + if (_activeRef === "users") { + _pathContext = "users"; + return; + } + _pathContext = null; + } + function _pushUserPath(userId) { if (typeof window === "undefined") return; if (window.location.protocol === "file:") return; - if (_activeRef !== "users") return; - const target = userId ? `/admin/users/${userId}` : `/admin/users`; + let target = ""; + if (_activeRef === "users") { + target = userId ? `/admin/users/${userId}` : `/admin/users`; + } else if (_activeRef === "payments" && _pathContext === "payments") { + target = userId ? `/admin/payments/users/${userId}` : `/admin/payments`; + } + if (!target) return; if (window.location.pathname === target) return; window.history.pushState(null, "", `${target}${window.location.search}${window.location.hash}`); } @@ -94,6 +112,7 @@ export function createUsersStore({ api, onToast, at }) { const userId = typeof userOrId === "object" && userOrId !== null ? userOrId.user_id : Number(userOrId); if (!userId) return; + _setPathContext(opts.pathContext); state.update((s) => ({ ...s, @@ -136,6 +155,7 @@ export function createUsersStore({ api, onToast, at }) { onToast(res?.error || "load_failed"); state.update((s) => ({ ...s, openedUser: null })); if (!opts.skipPush) _pushUserPath(null); + _pathContext = null; } } finally { state.update((s) => ({ ...s, userDetailLoading: false })); @@ -162,6 +182,7 @@ export function createUsersStore({ api, onToast, at }) { }; }); if (wasOpen && !opts.skipPush) _pushUserPath(null); + _pathContext = null; } async function loadUserLogs(page) { diff --git a/frontend/src/lib/webapp/routes.js b/frontend/src/lib/webapp/routes.js index 96a97bf..ec01ea1 100644 --- a/frontend/src/lib/webapp/routes.js +++ b/frontend/src/lib/webapp/routes.js @@ -62,6 +62,14 @@ export function adminPaymentIdFromPath(pathname) { return m ? Number(m[1]) : null; } +export function adminPaymentsUserIdFromPath(pathname) { + const normalized = String(pathname || "") + .toLowerCase() + .replace(/\/+$/, ""); + const m = normalized.match(/^\/admin\/payments\/users\/(-?\d+)$/); + return m ? Number(m[1]) : null; +} + export function supportTicketIdFromPath(pathname) { const normalized = String(pathname || "") .toLowerCase() @@ -89,8 +97,12 @@ export function syncSectionPath(section, replace = false, adminSection = null, a const supportTicketId = adm === "support" ? adminSupportTicketIdFromPath(window.location.pathname) : null; const paymentId = adm === "payments" ? adminPaymentIdFromPath(window.location.pathname) : null; + const paymentUserId = + adm === "payments" ? adminPaymentsUserIdFromPath(window.location.pathname) : null; if (adm === "users" && uid) targetPath = `/admin/users/${uid}`; else if (adm === "support" && supportTicketId) targetPath = `/admin/support/${supportTicketId}`; + else if (adm === "payments" && paymentUserId) + targetPath = `/admin/payments/users/${paymentUserId}`; else if (adm === "payments" && paymentId) targetPath = `/admin/payments/${paymentId}`; else targetPath = `/admin/${adm}`; }