diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 455c765..82e7153 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -101,6 +101,7 @@ sectionFromPath, supportTicketIdFromPath, syncSectionPath, + withRoutePrefix, } from "./lib/webapp/routes.js"; export let mockRuntime = null; @@ -125,6 +126,8 @@ const MOCK_SOURCE = mockRuntime?.source || EMPTY_MOCK; const previewBoardComponent = mockRuntime?.PreviewBoard || null; const isDocsDemo = mockRuntime?.docsDemo === true; + const routePrefix = isDocsDemo ? "/demo/runtime" : ""; + let docsDemoParentRouteConsumed = false; const query = new URLSearchParams(window.location.search); const isAppLaunchRoute = isExternalAppLaunchPath(window.location.pathname); mockRuntime?.applyPreviewMock?.(query.get("mock")); @@ -257,7 +260,7 @@ telegramSdk, }); const devicesStore = createDevicesStore({ api, t, showToast }); - const supportStore = createSupportStore({ api, t, showToast }); + const supportStore = createSupportStore({ api, t, showToast, routePrefix }); const installGuidesStore = createInstallGuidesStore({ api, t, showToast }); const accountStore = createAccountStore({ api, @@ -773,7 +776,7 @@ const section = isDocsDemo && currentQuery.get("screen") ? normalizeSection(currentQuery.get("screen")) - : sectionFromPath(window.location.pathname); + : sectionFromPath(routePathnameFromLocation(), routePrefix); if (mode === "login") { setPasswordLoginMode(isPasswordLoginPath(), true); screen = "login"; @@ -783,11 +786,11 @@ if (section === "admin" && isAdmin) { adminActiveSection = isDocsDemo ? initialAdminSectionFromLocation() - : adminSectionFromPath(window.location.pathname); + : adminSectionFromPath(routePathnameFromLocation(), routePrefix); const pathAtStart = window.location.pathname; void Promise.all([ensureI18nScope("admin"), ensureAdminBundle()]) .then(() => { - if (sectionFromPath(window.location.pathname) !== "admin") return; + if (sectionFromPath(routePathnameFromLocation(), routePrefix) !== "admin") return; if (window.location.pathname !== pathAtStart) return; activeTab = "settings"; screen = "admin"; @@ -1038,39 +1041,95 @@ return new URLSearchParams(window.location.search); } + function docsDemoParentSearchParams() { + if (!isDocsDemo) return null; + try { + if (window.parent === window) return null; + return new URLSearchParams(window.parent.location.search); + } catch (_error) { + return null; + } + } + + function normalizeDemoRoutePath(value) { + const raw = String(value || "").trim(); + if (!raw) return ""; + const withSlash = raw.startsWith("/") ? raw : `/${raw}`; + return withSlash.replace(/\/{2,}/g, "/").replace(/\/+$/, "") || "/"; + } + + function docsDemoRouteParams() { + if (!isDocsDemo) return null; + const currentQuery = currentSearchParams(); + const currentParams = { + path: currentQuery.get("path") || "", + screen: currentQuery.get("screen") || "", + adminSection: currentQuery.get("admin_section") || "", + }; + if (currentParams.path || currentParams.screen || currentParams.adminSection) { + return currentParams; + } + if (docsDemoParentRouteConsumed) return currentParams; + const parentQuery = docsDemoParentSearchParams(); + return { + path: parentQuery?.get("path") || "", + screen: parentQuery?.get("screen") || "", + adminSection: parentQuery?.get("admin_section") || "", + }; + } + + function docsDemoRoutePathFromParams() { + const params = docsDemoRouteParams(); + if (!params) return ""; + const explicitPath = normalizeDemoRoutePath(params.path); + if (explicitPath) return explicitPath; + const section = normalizeSection(params.screen); + if (section === "admin") { + return `/admin/${normalizeAdminSection(params.adminSection || "stats")}`; + } + return params.screen ? `/${section}` : ""; + } + + function routePathnameFromLocation() { + return docsDemoRoutePathFromParams() || window.location.pathname; + } + + function cleanDocsDemoRouteQuery() { + if (!isDocsDemo || window.location.protocol === "file:") return; + const url = new URL(window.location.href); + const routeKeys = ["path", "screen", "admin_section"]; + const changed = routeKeys.some((key) => url.searchParams.has(key)); + if (!changed) return; + for (const key of routeKeys) url.searchParams.delete(key); + const search = url.searchParams.toString(); + window.history.replaceState( + null, + "", + `${url.pathname}${search ? `?${search}` : ""}${url.hash}` + ); + } + function initialAdminSectionFromLocation() { const currentQuery = currentSearchParams(); if (MOCK && currentQuery.get("admin_section")) { return normalizeAdminSection(currentQuery.get("admin_section")); } - return adminSectionFromPath(window.location.pathname); + const demoRouteParams = docsDemoRouteParams(); + if (MOCK && demoRouteParams?.adminSection) { + return normalizeAdminSection(demoRouteParams.adminSection); + } + return adminSectionFromPath(routePathnameFromLocation(), routePrefix); } - function syncDocsDemoSection(section, replace = false, adminSection = null) { + function syncDocsDemoSection(section, replace = false, adminSection = null, adminUserId = 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); - } + syncSectionPath(section, replace, adminSection, adminUserId, routePrefix); + cleanDocsDemoRouteQuery(); return true; } function syncAppSectionPath(section, replace = false, adminSection = null, adminUserId = null) { - if (syncDocsDemoSection(section, replace, adminSection)) return; + if (syncDocsDemoSection(section, replace, adminSection, adminUserId)) return; syncSectionPath(section, replace, adminSection, adminUserId); } @@ -1079,14 +1138,15 @@ onClose: closeAdminPanel, onToast: (text) => showToast(text), initialSection: screen === "admin" ? adminActiveSection : initialAdminSectionFromLocation(), - initialPaymentId: adminPaymentIdFromPath(window.location.pathname), - initialPaymentUserId: adminPaymentsUserIdFromPath(window.location.pathname), - initialUserId: adminUserIdFromPath(window.location.pathname), + initialPaymentId: adminPaymentIdFromPath(routePathnameFromLocation(), routePrefix), + initialPaymentUserId: adminPaymentsUserIdFromPath(routePathnameFromLocation(), routePrefix), + initialUserId: adminUserIdFromPath(routePathnameFromLocation(), routePrefix), onSectionChange: handleAdminSectionChange, onSettingsSaved: handleAdminPersistedSaved, onTariffsSaved: handleAdminPersistedSaved, onThemesSaved: handleAdminPersistedSaved, onTranslationsSaved: handleAdminTranslationsSaved, + routePrefix, brandTitle, brand, appFaviconUrl: CFG.faviconUrl, @@ -1231,7 +1291,7 @@ ? preservedSection : MOCK && currentQuery.get("screen") ? normalizeSection(currentQuery.get("screen")) - : sectionFromPath(window.location.pathname); + : sectionFromPath(routePathnameFromLocation(), routePrefix); if (section === "admin" && !payload.user?.is_admin) section = "settings"; if (section === "devices" && !payload.settings?.my_devices_enabled) section = "home"; if (section === "support" && payload.settings?.support_tickets_enabled === false) { @@ -1258,7 +1318,10 @@ } } const initialSupportTicketId = - section === "support" ? supportTicketIdFromPath(window.location.pathname) : null; + section === "support" + ? supportTicketIdFromPath(routePathnameFromLocation(), routePrefix) + : null; + if (isDocsDemo) docsDemoParentRouteConsumed = true; activeTab = section === "admin" ? "settings" @@ -1275,10 +1338,8 @@ } supportStore.startPolling({ includeList: false }); } - if (isDocsDemo) { - // The docs demo is a static iframe route; keep query params as its navigation contract. - } else if (section === "support" && initialSupportTicketId) { - const targetPath = `/support/${initialSupportTicketId}`; + if (section === "support" && initialSupportTicketId) { + const targetPath = withRoutePrefix(`/support/${initialSupportTicketId}`, routePrefix); if (window.location.protocol !== "file:" && window.location.pathname !== targetPath) { window.history.replaceState( null, @@ -1286,6 +1347,7 @@ `${targetPath}${window.location.search}${window.location.hash}` ); } + cleanDocsDemoRouteQuery(); } else { syncAppSectionPath(section, true, initialAdminSection); } @@ -1710,7 +1772,7 @@ clearLanguageClickGuard(); billingStore.closePaymentModal(); const nextAdminSection = normalizeAdminSection( - adminActiveSection || adminSectionFromPath(window.location.pathname) + adminActiveSection || adminSectionFromPath(routePathnameFromLocation(), routePrefix) ); try { await ensureI18nScope("admin"); @@ -1737,20 +1799,7 @@ const nextAdminSection = normalizeAdminSection(adminSection); adminActiveSection = nextAdminSection; if (window.location.protocol === "file:") return; - if (isDocsDemo) { - syncDocsDemoSection("admin", false, nextAdminSection); - return; - } - const targetPath = - nextAdminSection === "users" && adminUserId - ? `/admin/users/${adminUserId}` - : `/admin/${nextAdminSection}`; - if (window.location.pathname === targetPath) return; - window.history.pushState( - null, - "", - `${targetPath}${window.location.search}${window.location.hash}` - ); + syncAppSectionPath("admin", false, nextAdminSection, adminUserId); } function adminPayloadHasLogoChange(options = {}) { diff --git a/frontend/src/admin/AdminPanel.svelte b/frontend/src/admin/AdminPanel.svelte index 67a48c9..f7d7d53 100644 --- a/frontend/src/admin/AdminPanel.svelte +++ b/frontend/src/admin/AdminPanel.svelte @@ -77,6 +77,7 @@ userTelegramProfileLink, userTelegramProfileLinkKind, } from "../lib/admin/users.js"; + import { stripRoutePrefix } from "../lib/webapp/routes.js"; export let api; export let onClose = () => {}; @@ -90,6 +91,7 @@ export let onTariffsSaved = () => {}; export let onThemesSaved = () => {}; export let onTranslationsSaved = () => {}; + export let routePrefix = ""; export let brand = {}; export let brandTitle = "/minishop"; export let appFaviconUrl = ""; @@ -243,15 +245,15 @@ const backupsStore = createBackupsStore({ api, onToast: flash, at }); const broadcastStore = createBroadcastStore({ api, onToast: flash, at }); const logsStore = createLogsStore({ api, at }); - const paymentsStore = createPaymentsStore({ api, onToast: flash, at }); + const paymentsStore = createPaymentsStore({ api, onToast: flash, at, routePrefix }); const promosStore = createPromosStore({ api, onToast: flash, at }); const settingsStore = createSettingsStore({ api, onToast: flash, at }); const statsStore = createStatsStore({ api, onToast: flash, at }); - const supportStore = createAdminSupportStore({ api, onToast: flash, at }); + const supportStore = createAdminSupportStore({ api, onToast: flash, at, routePrefix }); const tariffsStore = createTariffsStore({ api, onToast: flash, onTariffsSaved, flash, at }); const themesStore = createThemesStore({ api, onThemesSaved, flash, at }); const translationsStore = createTranslationsStore({ api, onToast: flash, at }); - const usersStore = createUsersStore({ api, onToast: flash, at }); + const usersStore = createUsersStore({ api, onToast: flash, at, routePrefix }); setContext("promosStore", promosStore); setContext("adsStore", adsStore); @@ -296,33 +298,38 @@ onLanguageChange(value, { section: "admin", adminSection: active }); } + function currentRoutePathname() { + if (typeof window === "undefined") return "/"; + return stripRoutePrefix(window.location.pathname, routePrefix); + } + function readSectionFromPath() { if (typeof window === "undefined") return "stats"; - const match = window.location.pathname.match(/^\/admin\/([a-z0-9_-]+)(?:\/.*)?$/i); + const match = currentRoutePathname().match(/^\/admin\/([a-z0-9_-]+)(?:\/.*)?$/i); return normalizeSection(match ? match[1].toLowerCase() : "stats"); } function readUserIdFromPath() { if (typeof window === "undefined") return null; - const match = window.location.pathname.match(/^\/admin\/users\/(-?\d+)$/); + const match = currentRoutePathname().match(/^\/admin\/users\/(-?\d+)$/); return match ? Number(match[1]) : null; } function readSupportTicketIdFromPath() { if (typeof window === "undefined") return null; - const match = window.location.pathname.match(/^\/admin\/support\/(\d+)$/); + const match = currentRoutePathname().match(/^\/admin\/support\/(\d+)$/); return match ? Number(match[1]) : null; } function readPaymentIdFromPath() { if (typeof window === "undefined") return null; - const match = window.location.pathname.match(/^\/admin\/payments\/(\d+)$/); + const match = currentRoutePathname().match(/^\/admin\/payments\/(\d+)$/); return match ? Number(match[1]) : null; } function readPaymentUserIdFromPath() { if (typeof window === "undefined") return null; - const match = window.location.pathname.match(/^\/admin\/payments\/users\/(-?\d+)$/); + const match = currentRoutePathname().match(/^\/admin\/payments\/users\/(-?\d+)$/); return match ? Number(match[1]) : null; } @@ -377,10 +384,26 @@ paymentsStore.closePayment({ skipPush: true }); onSectionChange(next); } + usersStore.setActive(next); paymentsStore.closePayment({ skipPush: true }); usersStore.openUser(uid, { pathContext: "payments" }); } + function openUserCard(userId) { + const uid = Number(userId); + if (!Number.isFinite(uid) || uid === 0) return; + const next = normalizeSection("users"); + sidebarOpen = false; + if (active !== next) { + active = next; + paymentsStore.closePayment({ skipPush: true }); + supportStore.closeTicketView({ skipPush: true }); + onSectionChange(next, uid); + } + usersStore.setActive(next); + usersStore.openUser(uid); + } + function resolvedAvatarUrl(user) { return userAvatarUrl(user) || (user?.email ? gravatarCache.gravatarUrl(user.email) : ""); } @@ -786,6 +809,7 @@ {at} {brand} {resolvedAvatarUrl} + onOpenUserCard={openUserCard} initialTicketId={readSupportTicketIdFromPath()} /> {/if} diff --git a/frontend/src/admin/sections/SupportSection.svelte b/frontend/src/admin/sections/SupportSection.svelte index a6cab42..86a8366 100644 --- a/frontend/src/admin/sections/SupportSection.svelte +++ b/frontend/src/admin/sections/SupportSection.svelte @@ -17,6 +17,7 @@ export let initialTicketId = null; export let brand = {}; export let resolvedAvatarUrl = () => ""; + export let onOpenUserCard = () => {}; const supportStore = getContext("adminSupportStore"); let reply = ""; @@ -306,7 +307,12 @@ onPatch={(updates) => supportStore.patchTicket(updates)} onClose={() => supportStore.closeTicket()} /> - + {}, at = (key, _params, fallback) => fallback || key, + routePrefix = "", }) { const state = writable({ payments: [], @@ -25,7 +27,10 @@ export function createPaymentsStore({ function pushPaymentPath(paymentId) { if (typeof window === "undefined" || window.location.protocol === "file:") return; if (active !== "payments") return; - const target = paymentId ? `/admin/payments/${paymentId}` : "/admin/payments"; + const target = withRoutePrefix( + paymentId ? `/admin/payments/${paymentId}` : "/admin/payments", + routePrefix + ); if (window.location.pathname === target) return; window.history.pushState(null, "", `${target}${window.location.search}${window.location.hash}`); } diff --git a/frontend/src/lib/admin/stores/supportStore.js b/frontend/src/lib/admin/stores/supportStore.js index 1528b64..6de6de8 100644 --- a/frontend/src/lib/admin/stores/supportStore.js +++ b/frontend/src/lib/admin/stores/supportStore.js @@ -1,6 +1,7 @@ import { writable } from "svelte/store"; +import { withRoutePrefix } from "../../webapp/routes.js"; -export function createAdminSupportStore({ api, onToast, at }) { +export function createAdminSupportStore({ api, onToast, at, routePrefix = "" }) { const OPEN_TICKET_POLL_MS = 3_000; const STATS_POLL_MS = 30_000; const HIDDEN_POLL_MS = 300_000; @@ -58,7 +59,10 @@ export function createAdminSupportStore({ api, onToast, at }) { function pushTicketPath(ticketId) { if (typeof window === "undefined" || window.location.protocol === "file:") return; if (active !== "support") return; - const target = ticketId ? `/admin/support/${ticketId}` : "/admin/support"; + const target = withRoutePrefix( + ticketId ? `/admin/support/${ticketId}` : "/admin/support", + routePrefix + ); if (window.location.pathname !== target) { window.history.pushState( null, diff --git a/frontend/src/lib/admin/stores/usersStore.js b/frontend/src/lib/admin/stores/usersStore.js index ae4d89b..4e58ad7 100644 --- a/frontend/src/lib/admin/stores/usersStore.js +++ b/frontend/src/lib/admin/stores/usersStore.js @@ -1,6 +1,7 @@ import { writable } from "svelte/store"; +import { withRoutePrefix } from "../../webapp/routes.js"; -export function createUsersStore({ api, onToast, at }) { +export function createUsersStore({ api, onToast, at, routePrefix = "" }) { const USERS_PAGE_SIZE = 25; const USER_LOGS_PAGE_SIZE = 20; @@ -70,6 +71,7 @@ export function createUsersStore({ api, onToast, at }) { target = userId ? `/admin/payments/users/${userId}` : `/admin/payments`; } if (!target) return; + target = withRoutePrefix(target, routePrefix); if (window.location.pathname === target) return; window.history.pushState(null, "", `${target}${window.location.search}${window.location.hash}`); } diff --git a/frontend/src/lib/components/patterns/admin/SupportUserContextPanel.svelte b/frontend/src/lib/components/patterns/admin/SupportUserContextPanel.svelte index 43927d2..d68c7bd 100644 --- a/frontend/src/lib/components/patterns/admin/SupportUserContextPanel.svelte +++ b/frontend/src/lib/components/patterns/admin/SupportUserContextPanel.svelte @@ -5,6 +5,7 @@ export let ticket; export let snapshot = {}; export let at = (key) => key; + export let onOpenUser = () => {}; $: user = ticket?.user || {}; $: displayName = snapshot?.name || user.username || user.email || user.user_id || "-"; @@ -65,7 +66,7 @@ variant="ghost" size="icon" disabled={!canOpenUser} - onclick={() => (window.location.href = `/admin/users/${user.user_id}`)} + onclick={() => onOpenUser(user.user_id)} aria-label={at("support_open_user", {}, "Карточка")} title={at("support_open_user", {}, "Карточка")} > diff --git a/frontend/src/lib/webapp/routes.js b/frontend/src/lib/webapp/routes.js index 1c85c7c..2c69574 100644 --- a/frontend/src/lib/webapp/routes.js +++ b/frontend/src/lib/webapp/routes.js @@ -25,15 +25,41 @@ export function normalizeAdminSection(value) { return ADMIN_SECTIONS.has(section) ? section : "stats"; } -export function sectionFromPath(pathname) { +function normalizePathname(pathname) { + const normalized = String(pathname || "") + .trim() + .replace(/\/+$/, ""); + return normalized || "/"; +} + +export function stripRoutePrefix(pathname, routePrefix = "") { + const path = normalizePathname(pathname); + const prefix = normalizePathname(routePrefix); + if (prefix === "/") return path; + if (path.toLowerCase() === prefix.toLowerCase()) return "/"; + if (path.toLowerCase().startsWith(`${prefix.toLowerCase()}/`)) { + return path.slice(prefix.length) || "/"; + } + return path; +} + +export function withRoutePrefix(pathname, routePrefix = "") { + const path = normalizePathname(pathname); + const prefix = normalizePathname(routePrefix); + if (prefix === "/") return path; + if (path === "/") return prefix; + return `${prefix}${path}`; +} + +export function sectionFromPath(pathname, routePrefix = "") { const normalizedPath = String(pathname || "") .trim() - .toLowerCase() .replace(/\/+$/, ""); - if (!normalizedPath || normalizedPath === "/") return "home"; - if (normalizedPath === "/admin" || normalizedPath.startsWith("/admin/")) return "admin"; - if (normalizedPath === "/support" || normalizedPath.startsWith("/support/")) return "support"; - const section = normalizedPath.startsWith("/") ? normalizedPath.slice(1) : normalizedPath; + const routePath = stripRoutePrefix(normalizedPath, routePrefix).toLowerCase().replace(/\/+$/, ""); + if (!routePath || routePath === "/") return "home"; + if (routePath === "/admin" || routePath.startsWith("/admin/")) return "admin"; + if (routePath === "/support" || routePath.startsWith("/support/")) return "support"; + const section = routePath.startsWith("/") ? routePath.slice(1) : routePath; return normalizeSection(section); } @@ -45,67 +71,68 @@ export function publicInstallTokenFromPath(pathname) { return match ? match[1].toLowerCase() : ""; } -export function adminSectionFromPath(pathname) { - const normalized = String(pathname || "") - .toLowerCase() - .replace(/\/+$/, ""); +export function adminSectionFromPath(pathname, routePrefix = "") { + const normalized = stripRoutePrefix(pathname, routePrefix).toLowerCase().replace(/\/+$/, ""); const m = normalized.match(/^\/admin\/([a-z0-9_-]+)(?:\/.*)?$/); return normalizeAdminSection(m ? m[1] : ""); } -export function adminUserIdFromPath(pathname) { - const normalized = String(pathname || "") - .toLowerCase() - .replace(/\/+$/, ""); +export function adminUserIdFromPath(pathname, routePrefix = "") { + const normalized = stripRoutePrefix(pathname, routePrefix).toLowerCase().replace(/\/+$/, ""); const m = normalized.match(/^\/admin\/users\/(-?\d+)$/); return m ? Number(m[1]) : null; } -export function adminPaymentIdFromPath(pathname) { - const normalized = String(pathname || "") - .toLowerCase() - .replace(/\/+$/, ""); +export function adminPaymentIdFromPath(pathname, routePrefix = "") { + const normalized = stripRoutePrefix(pathname, routePrefix).toLowerCase().replace(/\/+$/, ""); const m = normalized.match(/^\/admin\/payments\/(\d+)$/); return m ? Number(m[1]) : null; } -export function adminPaymentsUserIdFromPath(pathname) { - const normalized = String(pathname || "") - .toLowerCase() - .replace(/\/+$/, ""); +export function adminPaymentsUserIdFromPath(pathname, routePrefix = "") { + const normalized = stripRoutePrefix(pathname, routePrefix).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() - .replace(/\/+$/, ""); +export function supportTicketIdFromPath(pathname, routePrefix = "") { + const normalized = stripRoutePrefix(pathname, routePrefix).toLowerCase().replace(/\/+$/, ""); const m = normalized.match(/^\/support\/(\d+)$/); return m ? Number(m[1]) : null; } -export function adminSupportTicketIdFromPath(pathname) { - const normalized = String(pathname || "") - .toLowerCase() - .replace(/\/+$/, ""); +export function adminSupportTicketIdFromPath(pathname, routePrefix = "") { + const normalized = stripRoutePrefix(pathname, routePrefix).toLowerCase().replace(/\/+$/, ""); const m = normalized.match(/^\/admin\/support\/(\d+)$/); return m ? Number(m[1]) : null; } -export function syncSectionPath(section, replace = false, adminSection = null, adminUserId = null) { +export function syncSectionPath( + section, + replace = false, + adminSection = null, + adminUserId = null, + routePrefix = "" +) { if (window.location.protocol === "file:") return; const normalized = normalizeSection(section); let targetPath = APP_SECTION_PATHS[normalized] || APP_SECTION_PATHS.home; if (normalized === "admin") { - const adm = adminSection || adminSectionFromPath(window.location.pathname) || "stats"; + const adm = + adminSection || adminSectionFromPath(window.location.pathname, routePrefix) || "stats"; const uid = - adminUserId ?? (adm === "users" ? adminUserIdFromPath(window.location.pathname) : null); + adminUserId ?? + (adm === "users" ? adminUserIdFromPath(window.location.pathname, routePrefix) : null); const supportTicketId = - adm === "support" ? adminSupportTicketIdFromPath(window.location.pathname) : null; - const paymentId = adm === "payments" ? adminPaymentIdFromPath(window.location.pathname) : null; + adm === "support" + ? adminSupportTicketIdFromPath(window.location.pathname, routePrefix) + : null; + const paymentId = + adm === "payments" ? adminPaymentIdFromPath(window.location.pathname, routePrefix) : null; const paymentUserId = - adm === "payments" ? adminPaymentsUserIdFromPath(window.location.pathname) : null; + adm === "payments" + ? adminPaymentsUserIdFromPath(window.location.pathname, routePrefix) + : null; if (adm === "users" && uid) targetPath = `/admin/users/${uid}`; else if (adm === "support" && supportTicketId) targetPath = `/admin/support/${supportTicketId}`; else if (adm === "payments" && paymentUserId) @@ -113,6 +140,7 @@ export function syncSectionPath(section, replace = false, adminSection = null, a else if (adm === "payments" && paymentId) targetPath = `/admin/payments/${paymentId}`; else targetPath = `/admin/${adm}`; } + targetPath = withRoutePrefix(targetPath, routePrefix); if (window.location.pathname === targetPath) return; const nextUrl = `${targetPath}${window.location.search}${window.location.hash}`; window.history[replace ? "replaceState" : "pushState"](null, "", nextUrl); diff --git a/frontend/src/lib/webapp/stores/supportStore.js b/frontend/src/lib/webapp/stores/supportStore.js index 95d7202..1653d48 100644 --- a/frontend/src/lib/webapp/stores/supportStore.js +++ b/frontend/src/lib/webapp/stores/supportStore.js @@ -1,6 +1,7 @@ import { writable } from "svelte/store"; +import { withRoutePrefix } from "../routes.js"; -export function createSupportStore({ api, t, showToast }) { +export function createSupportStore({ api, t, showToast, routePrefix = "" }) { const OPEN_TICKET_POLL_MS = 3_000; const ACTIVE_POLL_MS = 8_000; const BACKGROUND_POLL_MS = 45_000; @@ -198,7 +199,7 @@ export function createSupportStore({ api, t, showToast }) { detailLoading: true, })); if (!opts.skipPush && typeof window !== "undefined" && window.location.protocol !== "file:") { - const target = `/support/${id}`; + const target = withRoutePrefix(`/support/${id}`, routePrefix); if (window.location.pathname !== target) { window.history.pushState( null, @@ -232,11 +233,12 @@ export function createSupportStore({ api, t, showToast }) { function closeTicketView(opts = {}) { state.update((s) => ({ ...s, openedTicketId: null, openedTicket: null, messages: [] })); if (!opts.skipPush && typeof window !== "undefined" && window.location.protocol !== "file:") { - if (window.location.pathname.startsWith("/support/")) { + const supportPath = withRoutePrefix("/support", routePrefix); + if (window.location.pathname.startsWith(`${supportPath}/`)) { window.history.pushState( null, "", - `/support${window.location.search}${window.location.hash}` + `${supportPath}${window.location.search}${window.location.hash}` ); } }