fix(router): add SPA fallback and tab URL sync
This commit is contained in:
1 parent
32f5ed8d6b
commit
00730bb375
2 files changed
+68
-3
No files matched your search
+48
-1
@@ -40,6 +40,31 @@ if (!root) {
|
||||
throw new Error("Missing #app root");
|
||||
}
|
||||
|
||||
const normalizePath = (path: string) => {
|
||||
if (path.length <= 1) {
|
||||
return "/";
|
||||
}
|
||||
return path.replace(/\/+$/, "") || "/";
|
||||
};
|
||||
|
||||
const tabToPath: Record<TabId, string> = {
|
||||
dashboard: "/",
|
||||
channels: "/channels",
|
||||
models: "/models",
|
||||
tokens: "/tokens",
|
||||
usage: "/usage",
|
||||
settings: "/settings",
|
||||
};
|
||||
|
||||
const pathToTab: Record<string, TabId> = {
|
||||
"/": "dashboard",
|
||||
"/channels": "channels",
|
||||
"/models": "models",
|
||||
"/tokens": "tokens",
|
||||
"/usage": "usage",
|
||||
"/settings": "settings",
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the admin console application.
|
||||
*
|
||||
@@ -50,7 +75,13 @@ const App = () => {
|
||||
const [token, setToken] = useState<string | null>(() =>
|
||||
localStorage.getItem("admin_token"),
|
||||
);
|
||||
const [activeTab, setActiveTab] = useState<TabId>("dashboard");
|
||||
const [activeTab, setActiveTab] = useState<TabId>(() => {
|
||||
if (typeof window === "undefined") {
|
||||
return "dashboard";
|
||||
}
|
||||
const normalized = normalizePath(window.location.pathname);
|
||||
return pathToTab[normalized] ?? "dashboard";
|
||||
});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [notice, setNotice] = useState("");
|
||||
const [data, setData] = useState<AdminData>(initialData);
|
||||
@@ -161,6 +192,17 @@ const App = () => {
|
||||
}
|
||||
}, [token, activeTab, loadTab]);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePopState = () => {
|
||||
const normalized = normalizePath(window.location.pathname);
|
||||
setActiveTab(pathToTab[normalized] ?? "dashboard");
|
||||
};
|
||||
window.addEventListener("popstate", handlePopState);
|
||||
return () => {
|
||||
window.removeEventListener("popstate", handlePopState);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data.settings) {
|
||||
return;
|
||||
@@ -227,6 +269,11 @@ const App = () => {
|
||||
}, []);
|
||||
|
||||
const handleTabChange = useCallback((tabId: TabId) => {
|
||||
const nextPath = tabToPath[tabId];
|
||||
const normalized = normalizePath(window.location.pathname);
|
||||
if (normalized !== nextPath) {
|
||||
history.pushState(null, "", nextPath);
|
||||
}
|
||||
setActiveTab(tabId);
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ app.route("/api/group", newapiGroupRoutes);
|
||||
|
||||
app.route("/v1", proxyRoutes);
|
||||
|
||||
app.notFound((c) => {
|
||||
app.notFound(async (c) => {
|
||||
const path = c.req.path;
|
||||
if (
|
||||
path === "/api" ||
|
||||
@@ -124,7 +124,25 @@ app.notFound((c) => {
|
||||
const assets = (
|
||||
c.env as { ASSETS?: { fetch: (input: Request) => Promise<Response> } }
|
||||
).ASSETS;
|
||||
return assets ? assets.fetch(c.req.raw) : c.text("Not Found", 404);
|
||||
if (!assets) {
|
||||
return c.text("Not Found", 404);
|
||||
}
|
||||
|
||||
const res = await assets.fetch(c.req.raw);
|
||||
if (res.status !== 404) {
|
||||
return res;
|
||||
}
|
||||
|
||||
const accept = c.req.header("accept") ?? "";
|
||||
const isHtml = accept.includes("text/html");
|
||||
const isFile = path.includes(".");
|
||||
if (!isHtml || isFile) {
|
||||
return res;
|
||||
}
|
||||
|
||||
const url = new URL(c.req.url);
|
||||
url.pathname = "/index.html";
|
||||
return assets.fetch(new Request(url.toString(), c.req.raw));
|
||||
});
|
||||
|
||||
export default app;
|
||||
Reference in new issue
Block a user