fix: preserve admin view on language change

This commit is contained in:
3252a8
2026-05-28 09:54:27 +03:00
parent 6c0d2932c3
commit 12e60629fe
3 changed files with 105 additions and 28 deletions
+91 -23
View File
@@ -187,7 +187,9 @@
let adminBundleError = ""; let adminBundleError = "";
let adminMountTarget = null; let adminMountTarget = null;
let adminMountHandle = null; let adminMountHandle = null;
let adminMountedTarget = null;
let adminPanelProps = {}; let adminPanelProps = {};
let adminActiveSection = "stats";
let tg = null; let tg = null;
const telegramSdk = createTelegramSdk({ const telegramSdk = createTelegramSdk({
scriptUrl: TELEGRAM_WEBAPP_SCRIPT_URL, scriptUrl: TELEGRAM_WEBAPP_SCRIPT_URL,
@@ -767,7 +769,11 @@
void boot(); void boot();
return; return;
} }
const section = sectionFromPath(window.location.pathname); const currentQuery = currentSearchParams();
const section =
isDocsDemo && currentQuery.get("screen")
? normalizeSection(currentQuery.get("screen"))
: sectionFromPath(window.location.pathname);
if (mode === "login") { if (mode === "login") {
setPasswordLoginMode(isPasswordLoginPath(), true); setPasswordLoginMode(isPasswordLoginPath(), true);
screen = "login"; screen = "login";
@@ -775,6 +781,9 @@
} }
if (mode === "app") { if (mode === "app") {
if (section === "admin" && isAdmin) { if (section === "admin" && isAdmin) {
adminActiveSection = isDocsDemo
? initialAdminSectionFromLocation()
: adminSectionFromPath(window.location.pathname);
const pathAtStart = window.location.pathname; const pathAtStart = window.location.pathname;
void Promise.all([ensureI18nScope("admin"), ensureAdminBundle()]) void Promise.all([ensureI18nScope("admin"), ensureAdminBundle()])
.then(() => { .then(() => {
@@ -1022,20 +1031,54 @@
if (!adminMountHandle) return; if (!adminMountHandle) return;
adminMountHandle.destroy?.(); adminMountHandle.destroy?.();
adminMountHandle = null; adminMountHandle = null;
adminMountedTarget = null;
}
function currentSearchParams() {
return new URLSearchParams(window.location.search);
} }
function initialAdminSectionFromLocation() { function initialAdminSectionFromLocation() {
if (MOCK && query.get("admin_section")) { const currentQuery = currentSearchParams();
return normalizeAdminSection(query.get("admin_section")); if (MOCK && currentQuery.get("admin_section")) {
return normalizeAdminSection(currentQuery.get("admin_section"));
} }
return adminSectionFromPath(window.location.pathname); return adminSectionFromPath(window.location.pathname);
} }
function syncDocsDemoSection(section, replace = false, adminSection = null) {
if (!isDocsDemo || window.location.protocol === "file:") return false;
const normalized = normalizeSection(section);
const currentQuery = currentSearchParams();
currentQuery.set("screen", normalized);
if (normalized === "admin") {
currentQuery.set(
"admin_section",
normalizeAdminSection(
adminSection || adminActiveSection || initialAdminSectionFromLocation()
)
);
} else {
currentQuery.delete("admin_section");
}
const nextSearch = currentQuery.toString();
const nextUrl = `${window.location.pathname}${nextSearch ? `?${nextSearch}` : ""}${window.location.hash}`;
if (nextUrl !== `${window.location.pathname}${window.location.search}${window.location.hash}`) {
window.history[replace ? "replaceState" : "pushState"](null, "", nextUrl);
}
return true;
}
function syncAppSectionPath(section, replace = false, adminSection = null, adminUserId = null) {
if (syncDocsDemoSection(section, replace, adminSection)) return;
syncSectionPath(section, replace, adminSection, adminUserId);
}
$: adminPanelProps = { $: adminPanelProps = {
api, api,
onClose: closeAdminPanel, onClose: closeAdminPanel,
onToast: (text) => showToast(text), onToast: (text) => showToast(text),
initialSection: initialAdminSectionFromLocation(), initialSection: screen === "admin" ? adminActiveSection : initialAdminSectionFromLocation(),
initialPaymentId: adminPaymentIdFromPath(window.location.pathname), initialPaymentId: adminPaymentIdFromPath(window.location.pathname),
initialPaymentUserId: adminPaymentsUserIdFromPath(window.location.pathname), initialPaymentUserId: adminPaymentsUserIdFromPath(window.location.pathname),
initialUserId: adminUserIdFromPath(window.location.pathname), initialUserId: adminUserIdFromPath(window.location.pathname),
@@ -1063,11 +1106,13 @@
if (shouldMountAdmin) { if (shouldMountAdmin) {
try { try {
if (adminMountHandle) { if (adminMountHandle && adminMountedTarget === adminMountTarget) {
adminMountHandle.update?.(props); adminMountHandle.update?.(props);
} else { } else {
destroyAdminMount();
adminMountTarget.replaceChildren(); adminMountTarget.replaceChildren();
adminMountHandle = adminBundleApi.mount(adminMountTarget, props); adminMountHandle = adminBundleApi.mount(adminMountTarget, props);
adminMountedTarget = adminMountTarget;
} }
} catch (error) { } catch (error) {
adminBundleError = error?.message || "admin_bundle_mount_failed"; adminBundleError = error?.message || "admin_bundle_mount_failed";
@@ -1161,6 +1206,16 @@
} }
async function loadData(options = {}) { async function loadData(options = {}) {
const preserveView = options?.preserveView === true;
const preservedSection = preserveView
? normalizeSection(options?.section || screen || activeTab)
: null;
const preservedAdminSection =
preserveView && preservedSection === "admin"
? normalizeAdminSection(
options?.adminSection || adminActiveSection || initialAdminSectionFromLocation()
)
: null;
const payload = await api(options?.fresh ? "/me?fresh=1" : "/me"); const payload = await api(options?.fresh ? "/me?fresh=1" : "/me");
if (!payload.ok) throw new Error(payload.error || "load_failed"); if (!payload.ok) throw new Error(payload.error || "load_failed");
data = payload; data = payload;
@@ -1171,9 +1226,11 @@
paymentStep: "tariff", paymentStep: "tariff",
selectedMethod: payload.payment_methods?.[0]?.id || "", selectedMethod: payload.payment_methods?.[0]?.id || "",
})); }));
let section = const currentQuery = currentSearchParams();
MOCK && query.get("screen") let section = preserveView
? normalizeSection(query.get("screen")) ? preservedSection
: MOCK && currentQuery.get("screen")
? normalizeSection(currentQuery.get("screen"))
: sectionFromPath(window.location.pathname); : sectionFromPath(window.location.pathname);
if (section === "admin" && !payload.user?.is_admin) section = "settings"; if (section === "admin" && !payload.user?.is_admin) section = "settings";
if (section === "devices" && !payload.settings?.my_devices_enabled) section = "home"; if (section === "devices" && !payload.settings?.my_devices_enabled) section = "home";
@@ -1186,11 +1243,13 @@
) { ) {
section = "home"; section = "home";
} }
const initialAdminSection = section === "admin" ? initialAdminSectionFromLocation() : null; const initialAdminSection =
section === "admin" ? preservedAdminSection || initialAdminSectionFromLocation() : null;
if (section === "admin" && payload.user?.is_admin) { if (section === "admin" && payload.user?.is_admin) {
try { try {
await ensureI18nScope("admin"); await ensureI18nScope("admin");
await ensureAdminBundle(); await ensureAdminBundle();
adminActiveSection = initialAdminSection || "stats";
} catch (_error) { } catch (_error) {
void _error; void _error;
section = "settings"; section = "settings";
@@ -1228,7 +1287,7 @@
); );
} }
} else { } else {
syncSectionPath(section, true, initialAdminSection); syncAppSectionPath(section, true, initialAdminSection);
} }
if (section === "devices" && payload.settings?.my_devices_enabled) { if (section === "devices" && payload.settings?.my_devices_enabled) {
await devicesStore.loadDevices(true); await devicesStore.loadDevices(true);
@@ -1443,12 +1502,12 @@
activeTab = "home"; activeTab = "home";
if (useInstallGuides) { if (useInstallGuides) {
screen = "install"; screen = "install";
syncSectionPath("install", replace); syncAppSectionPath("install", replace);
installGuidesStore.load(true); installGuidesStore.load(true);
return; return;
} }
screen = "home"; screen = "home";
syncSectionPath("home", replace); syncAppSectionPath("home", replace);
} }
async function handleSubscriptionActivated(context = {}) { async function handleSubscriptionActivated(context = {}) {
@@ -1551,7 +1610,7 @@
billingStore.closePaymentModal(); billingStore.closePaymentModal();
activeTab = "home"; activeTab = "home";
screen = "home"; screen = "home";
syncSectionPath("home"); syncAppSectionPath("home");
} }
function goInstall() { function goInstall() {
@@ -1562,7 +1621,7 @@
billingStore.closePaymentModal(); billingStore.closePaymentModal();
activeTab = "home"; activeTab = "home";
screen = "install"; screen = "install";
syncSectionPath("install"); syncAppSectionPath("install");
installGuidesStore.load(true); installGuidesStore.load(true);
} }
@@ -1570,7 +1629,7 @@
billingStore.closePaymentModal(); billingStore.closePaymentModal();
activeTab = "invite"; activeTab = "invite";
screen = "invite"; screen = "invite";
syncSectionPath("invite"); syncAppSectionPath("invite");
} }
function goDevices() { function goDevices() {
@@ -1578,7 +1637,7 @@
billingStore.closePaymentModal(); billingStore.closePaymentModal();
activeTab = "devices"; activeTab = "devices";
screen = "devices"; screen = "devices";
syncSectionPath("devices"); syncAppSectionPath("devices");
devicesStore.loadDevices(devicesEnabled); devicesStore.loadDevices(devicesEnabled);
} }
@@ -1587,7 +1646,7 @@
billingStore.closePaymentModal(); billingStore.closePaymentModal();
activeTab = "support"; activeTab = "support";
screen = "support"; screen = "support";
syncSectionPath("support"); syncAppSectionPath("support");
supportStore.loadList(); supportStore.loadList();
supportStore.startPolling({ includeList: true }); supportStore.startPolling({ includeList: true });
} }
@@ -1643,13 +1702,16 @@
billingStore.closePaymentModal(); billingStore.closePaymentModal();
activeTab = "settings"; activeTab = "settings";
screen = "settings"; screen = "settings";
syncSectionPath("settings"); syncAppSectionPath("settings");
} }
async function openAdminPanel() { async function openAdminPanel() {
if (!isAdmin) return; if (!isAdmin) return;
clearLanguageClickGuard(); clearLanguageClickGuard();
billingStore.closePaymentModal(); billingStore.closePaymentModal();
const nextAdminSection = normalizeAdminSection(
adminActiveSection || adminSectionFromPath(window.location.pathname)
);
try { try {
await ensureI18nScope("admin"); await ensureI18nScope("admin");
await ensureAdminBundle(); await ensureAdminBundle();
@@ -1660,23 +1722,29 @@
} }
activeTab = "settings"; activeTab = "settings";
screen = "admin"; screen = "admin";
syncSectionPath("admin", false, adminSectionFromPath(window.location.pathname)); adminActiveSection = nextAdminSection;
syncAppSectionPath("admin", false, adminActiveSection);
} }
function closeAdminPanel() { function closeAdminPanel() {
screen = "settings"; screen = "settings";
activeTab = "settings"; activeTab = "settings";
syncSectionPath("settings"); syncAppSectionPath("settings");
} }
function handleAdminSectionChange(adminSection, adminUserId = null) { function handleAdminSectionChange(adminSection, adminUserId = null) {
if (screen !== "admin") return; if (screen !== "admin") return;
const nextAdminSection = normalizeAdminSection(adminSection);
adminActiveSection = nextAdminSection;
if (window.location.protocol === "file:") return; if (window.location.protocol === "file:") return;
if (isDocsDemo) return; if (isDocsDemo) {
syncDocsDemoSection("admin", false, nextAdminSection);
return;
}
const targetPath = const targetPath =
adminSection === "users" && adminUserId nextAdminSection === "users" && adminUserId
? `/admin/users/${adminUserId}` ? `/admin/users/${adminUserId}`
: `/admin/${adminSection}`; : `/admin/${nextAdminSection}`;
if (window.location.pathname === targetPath) return; if (window.location.pathname === targetPath) return;
window.history.pushState( window.history.pushState(
null, null,
+12 -3
View File
@@ -211,8 +211,13 @@
const normalizeSection = (value) => ((VALID_SECTIONS || []).includes(value) ? value : "stats"); const normalizeSection = (value) => ((VALID_SECTIONS || []).includes(value) ? value : "stats");
let active = normalizeSection(initialSection); let active = normalizeSection(initialSection);
$: if (initialSection) { let lastInitialSection = active;
active = normalizeSection(initialSection); $: {
const nextInitialSection = normalizeSection(initialSection);
if (nextInitialSection !== lastInitialSection) {
active = nextInitialSection;
lastInitialSection = nextInitialSection;
}
} }
let sidebarOpen = false; let sidebarOpen = false;
let isCompact = false; let isCompact = false;
@@ -287,6 +292,10 @@
onSectionChange(next); onSectionChange(next);
} }
function changeLanguage(value) {
onLanguageChange(value, { section: "admin", adminSection: active });
}
function readSectionFromPath() { function readSectionFromPath() {
if (typeof window === "undefined") return "stats"; if (typeof window === "undefined") return "stats";
const match = window.location.pathname.match(/^\/admin\/([a-z0-9_-]+)(?:\/.*)?$/i); const match = window.location.pathname.match(/^\/admin\/([a-z0-9_-]+)(?:\/.*)?$/i);
@@ -584,7 +593,7 @@
items={languageOptions} items={languageOptions}
disabled={languageBusy} disabled={languageBusy}
onOpenChange={setAdminLanguageMenuOpen} onOpenChange={setAdminLanguageMenuOpen}
onValueChange={onLanguageChange} onValueChange={changeLanguage}
> >
<Select.Trigger <Select.Trigger
class="admin-language-trigger" class="admin-language-trigger"
@@ -346,7 +346,7 @@ export function createAccountStore({
window.location.assign(buildTelegramOAuthStartUrl("link", getTg())); window.location.assign(buildTelegramOAuthStartUrl("link", getTg()));
} }
async function updateAccountLanguage(nextValue) { async function updateAccountLanguage(nextValue, options = {}) {
const s = get(state); const s = get(state);
const normalize = typeof normalizeLangCode === "function" ? normalizeLangCode : (v) => v; const normalize = typeof normalizeLangCode === "function" ? normalizeLangCode : (v) => v;
const language = normalize(nextValue); const language = normalize(nextValue);
@@ -361,7 +361,7 @@ export function createAccountStore({
if (typeof updateLocalData === "function") { if (typeof updateLocalData === "function") {
updateLocalData(normalize(response.language || language)); updateLocalData(normalize(response.language || language));
} }
await loadData({ fresh: true }); await loadData({ fresh: true, preserveView: true, ...options });
} catch { } catch {
showToast(t("wa_settings_language_update_failed")); showToast(t("wa_settings_language_update_failed"));
} finally { } finally {