feat(ui): migrate the self-contained leaf pages to App Router path routing

Bulk Wave B cutover for the 20 leaf pages whose only dependency is the auth
context (budgets, caching, cost-tracking, guardrails, guardrails-monitor, logs,
mcp-servers, memory, policies, projects, prompts, search-tools, skills,
tag-management, tool-policies, api-playground/transform-request, ui-theme,
vector-stores, workflows, access-groups).

Each gets a thin (dashboard)/<segment>/page.tsx that sources its data from
useAuthorized and renders the existing component; the legacy switch arm and
import are removed from page.tsx, and a map entry is added to MIGRATED_PAGES
(skills and claude-code-plugins both map to /ui/skills). A data-driven test
pins every forward mapping and reverse lookup.
This commit is contained in:
ryan-crabbe-berri 2026-06-08 14:47:15 -07:00
parent 69a7bdb247
commit ef8fa82136
23 changed files with 257 additions and 72 deletions

View file

@ -0,0 +1,7 @@
"use client";
import { AccessGroupsPage } from "@/components/AccessGroups/AccessGroupsPage";
export default function AccessGroupsRoute() {
return <AccessGroupsPage />;
}

View file

@ -0,0 +1,9 @@
"use client";
import BudgetPanel from "@/components/budgets/budget_panel";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function BudgetsRoute() {
const { accessToken } = useAuthorized();
return <BudgetPanel accessToken={accessToken} />;
}

View file

@ -0,0 +1,17 @@
"use client";
import CacheDashboard from "@/components/cache_dashboard";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function CachingRoute() {
const { accessToken, userId, userRole, token, premiumUser } = useAuthorized();
return (
<CacheDashboard
userID={userId}
userRole={userRole}
token={token}
accessToken={accessToken}
premiumUser={premiumUser}
/>
);
}

View file

@ -0,0 +1,9 @@
"use client";
import { CostTrackingSettings } from "@/components/CostTrackingSettings";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function CostTrackingRoute() {
const { accessToken, userId, userRole } = useAuthorized();
return <CostTrackingSettings userID={userId} userRole={userRole} accessToken={accessToken} />;
}

View file

@ -0,0 +1,9 @@
"use client";
import GuardrailsMonitorView from "@/components/GuardrailsMonitor/GuardrailsMonitorView";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function GuardrailsMonitorRoute() {
const { accessToken } = useAuthorized();
return <GuardrailsMonitorView accessToken={accessToken} />;
}

View file

@ -0,0 +1,9 @@
"use client";
import GuardrailsPanel from "@/components/guardrails";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function GuardrailsRoute() {
const { accessToken, userRole } = useAuthorized();
return <GuardrailsPanel accessToken={accessToken} userRole={userRole} />;
}

View file

@ -0,0 +1,17 @@
"use client";
import SpendLogsTable from "@/components/view_logs";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function LogsRoute() {
const { accessToken, userId, userRole, token, premiumUser } = useAuthorized();
return (
<SpendLogsTable
userID={userId}
userRole={userRole}
token={token}
accessToken={accessToken}
premiumUser={premiumUser}
/>
);
}

View file

@ -0,0 +1,9 @@
"use client";
import { MCPServers } from "@/components/mcp_tools";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function McpServersRoute() {
const { accessToken, userId, userRole } = useAuthorized();
return <MCPServers accessToken={accessToken} userRole={userRole} userID={userId} />;
}

View file

@ -0,0 +1,9 @@
"use client";
import { MemoryView } from "@/components/MemoryView";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function MemoryRoute() {
const { accessToken, userId, userRole } = useAuthorized();
return <MemoryView accessToken={accessToken} userID={userId} userRole={userRole} />;
}

View file

@ -0,0 +1,9 @@
"use client";
import PoliciesPanel from "@/components/policies";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function PoliciesRoute() {
const { accessToken, userRole } = useAuthorized();
return <PoliciesPanel accessToken={accessToken} userRole={userRole} />;
}

View file

@ -0,0 +1,7 @@
"use client";
import { ProjectsPage } from "@/components/Projects/ProjectsPage";
export default function ProjectsRoute() {
return <ProjectsPage />;
}

View file

@ -0,0 +1,9 @@
"use client";
import PromptsPanel from "@/components/prompts";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function PromptsRoute() {
const { accessToken, userRole } = useAuthorized();
return <PromptsPanel accessToken={accessToken} userRole={userRole} />;
}

View file

@ -0,0 +1,9 @@
"use client";
import { SearchTools } from "@/components/SearchTools";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function SearchToolsRoute() {
const { accessToken, userId, userRole } = useAuthorized();
return <SearchTools accessToken={accessToken} userRole={userRole} userID={userId} />;
}

View file

@ -0,0 +1,9 @@
"use client";
import ClaudeCodePluginsPanel from "@/components/claude_code_plugins";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function SkillsRoute() {
const { accessToken, userRole } = useAuthorized();
return <ClaudeCodePluginsPanel accessToken={accessToken} userRole={userRole} />;
}

View file

@ -0,0 +1,9 @@
"use client";
import TagManagement from "@/components/tag_management";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function TagManagementRoute() {
const { accessToken, userId, userRole } = useAuthorized();
return <TagManagement accessToken={accessToken} userRole={userRole} userID={userId} />;
}

View file

@ -0,0 +1,9 @@
"use client";
import ToolPoliciesView from "@/components/ToolPoliciesView";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function ToolPoliciesRoute() {
const { accessToken, userRole } = useAuthorized();
return <ToolPoliciesView accessToken={accessToken} userRole={userRole} />;
}

View file

@ -0,0 +1,9 @@
"use client";
import TransformRequestPanel from "@/components/transform_request";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function TransformRequestRoute() {
const { accessToken } = useAuthorized();
return <TransformRequestPanel accessToken={accessToken} />;
}

View file

@ -0,0 +1,9 @@
"use client";
import UIThemeSettings from "@/components/ui_theme_settings";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function UiThemeRoute() {
const { accessToken, userId, userRole } = useAuthorized();
return <UIThemeSettings userID={userId} userRole={userRole} accessToken={accessToken} />;
}

View file

@ -0,0 +1,9 @@
"use client";
import VectorStoreManagement from "@/components/vector_store_management";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function VectorStoresRoute() {
const { accessToken, userId, userRole } = useAuthorized();
return <VectorStoreManagement accessToken={accessToken} userRole={userRole} userID={userId} />;
}

View file

@ -0,0 +1,9 @@
"use client";
import WorkflowRuns from "@/components/workflow_runs";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function WorkflowsRoute() {
const { accessToken } = useAuthorized();
return <WorkflowRuns accessToken={accessToken} />;
}

View file

@ -5,18 +5,10 @@ import OldModelDashboard from "@/app/(dashboard)/models-and-endpoints/ModelsAndE
import PlaygroundPage from "@/app/(dashboard)/playground/page";
import AdminPanel from "@/components/AdminPanel";
import AgentsPanel from "@/components/agents";
import BudgetPanel from "@/components/budgets/budget_panel";
import CacheDashboard from "@/components/cache_dashboard";
import ClaudeCodePluginsPanel from "@/components/claude_code_plugins";
import { teamListCall as v2TeamListCall } from "@/app/(dashboard)/hooks/teams/useTeams";
import LoadingScreen from "@/components/common_components/LoadingScreen";
import { CostTrackingSettings } from "@/components/CostTrackingSettings";
import GeneralSettings from "@/components/general_settings";
import GuardrailsMonitorView from "@/components/GuardrailsMonitor/GuardrailsMonitorView";
import GuardrailsPanel from "@/components/guardrails";
import PoliciesPanel from "@/components/policies";
import { Team } from "@/components/key_team_helpers/key_list";
import { MCPServers } from "@/components/mcp_tools";
import ModelHubTable from "@/components/AIHub/ModelHubTable";
import Navbar from "@/components/navbar";
import { Organization, proxyBaseUrl, getInProductNudgesCall } from "@/components/networking";
@ -25,23 +17,11 @@ import OldTeams from "@/components/OldTeams";
import { fetchUserModels, CreateKeyPrefillData } from "@/components/organisms/create_key_button";
import Organizations, { fetchOrganizations } from "@/components/organizations";
import PassThroughSettings from "@/components/pass_through_settings";
import PromptsPanel from "@/components/prompts";
import PublicModelHub from "@/components/public_model_hub";
import { SearchTools } from "@/components/SearchTools";
import Settings from "@/components/settings";
import { SurveyPrompt, SurveyModal, ClaudeCodePrompt, ClaudeCodeModal } from "@/components/survey";
import TagManagement from "@/components/tag_management";
import TransformRequestPanel from "@/components/transform_request";
import UIThemeSettings from "@/components/ui_theme_settings";
import Usage from "@/components/usage";
import UserDashboard from "@/components/user_dashboard";
import { AccessGroupsPage } from "@/components/AccessGroups/AccessGroupsPage";
import { ProjectsPage } from "@/components/Projects/ProjectsPage";
import VectorStoreManagement from "@/components/vector_store_management";
import ToolPoliciesView from "@/components/ToolPoliciesView";
import { MemoryView } from "@/components/MemoryView";
import WorkflowRuns from "@/components/workflow_runs";
import SpendLogsTable from "@/components/view_logs";
import ViewUserDashboard from "@/components/view_users";
import { ThemeProvider } from "@/contexts/ThemeContext";
import { useAuth } from "@/contexts/AuthContext";
@ -442,18 +422,8 @@ function CreateKeyPageContent() {
<AdminPanel proxySettings={proxySettings} />
) : page == "logging-and-alerts" ? (
<Settings userID={userID} userRole={userRole} accessToken={accessToken} premiumUser={premiumUser} />
) : page == "budgets" ? (
<BudgetPanel accessToken={accessToken} />
) : page == "guardrails" ? (
<GuardrailsPanel accessToken={accessToken} userRole={userRole} />
) : page == "policies" ? (
<PoliciesPanel accessToken={accessToken} userRole={userRole} />
) : page == "agents" ? (
<AgentsPanel accessToken={accessToken} userRole={userRole} teams={teams} />
) : page == "prompts" ? (
<PromptsPanel accessToken={accessToken} userRole={userRole} />
) : page == "transform-request" ? (
<TransformRequestPanel accessToken={accessToken} />
) : page == "router-settings" ? (
<GeneralSettings
userID={userID}
@ -461,10 +431,6 @@ function CreateKeyPageContent() {
accessToken={accessToken}
modelData={modelData}
/>
) : page == "ui-theme" ? (
<UIThemeSettings userID={userID} userRole={userRole} accessToken={accessToken} />
) : page == "cost-tracking" ? (
<CostTrackingSettings userID={userID} userRole={userRole} accessToken={accessToken} />
) : page == "model-hub-table" ? (
isAdminRole(userRole) ? (
<ModelHubTable
@ -476,14 +442,6 @@ function CreateKeyPageContent() {
) : (
<PublicModelHub accessToken={accessToken} isEmbedded={true} />
)
) : page == "caching" ? (
<CacheDashboard
userID={userID}
userRole={userRole}
token={token}
accessToken={accessToken}
premiumUser={premiumUser}
/>
) : page == "pass-through-settings" ? (
<PassThroughSettings
userID={userID}
@ -492,36 +450,6 @@ function CreateKeyPageContent() {
modelData={modelData}
premiumUser={premiumUser}
/>
) : page == "logs" ? (
<SpendLogsTable
userID={userID}
userRole={userRole}
token={token}
accessToken={accessToken}
premiumUser={premiumUser}
/>
) : page == "mcp-servers" ? (
<MCPServers accessToken={accessToken} userRole={userRole} userID={userID} />
) : page == "search-tools" ? (
<SearchTools accessToken={accessToken} userRole={userRole} userID={userID} />
) : page == "tag-management" ? (
<TagManagement accessToken={accessToken} userRole={userRole} userID={userID} />
) : page == "skills" || page == "claude-code-plugins" ? (
<ClaudeCodePluginsPanel accessToken={accessToken} userRole={userRole} />
) : page == "access-groups" ? (
<AccessGroupsPage />
) : page == "projects" ? (
<ProjectsPage />
) : page == "vector-stores" ? (
<VectorStoreManagement accessToken={accessToken} userRole={userRole} userID={userID} />
) : page == "tool-policies" ? (
<ToolPoliciesView accessToken={accessToken} userRole={userRole} />
) : page == "workflows" ? (
<WorkflowRuns accessToken={accessToken} />
) : page == "memory" ? (
<MemoryView accessToken={accessToken} userID={userID} userRole={userRole} />
) : page == "guardrails-monitor" ? (
<GuardrailsMonitorView accessToken={accessToken} />
) : page == "new_usage" ? (
<NewUsagePage
teams={(teams as Team[]) ?? []}

View file

@ -67,3 +67,47 @@ describe("legacyKeyForPathname", () => {
expect(legacyKeyForPathname("/ui/api-reference")).toBeNull();
});
});
describe("MIGRATED_PAGES leaf cutover", () => {
beforeEach(() => {
vi.resetModules();
});
it("routes every migrated leaf page to its segment and back to a sidebar key", async () => {
vi.doMock("@/components/networking", () => ({ serverRootPath: "/" }));
const { MIGRATED_PAGES, legacyKeyForPathname } = await import("./migratedPages");
const cases: [string, string][] = [
["budgets", "budgets"],
["caching", "caching"],
["cost-tracking", "cost-tracking"],
["guardrails", "guardrails"],
["guardrails-monitor", "guardrails-monitor"],
["logs", "logs"],
["mcp-servers", "mcp-servers"],
["memory", "memory"],
["policies", "policies"],
["projects", "projects"],
["prompts", "prompts"],
["search-tools", "search-tools"],
["skills", "skills"],
["claude-code-plugins", "skills"],
["tag-management", "tag-management"],
["tool-policies", "tool-policies"],
["transform-request", "transform-request"],
["ui-theme", "ui-theme"],
["vector-stores", "vector-stores"],
["workflows", "workflows"],
["access-groups", "access-groups"],
];
for (const [key, seg] of cases) {
expect(MIGRATED_PAGES[key]).toBe(seg);
expect(legacyKeyForPathname(`/ui/${seg}`)).not.toBeNull();
expect(legacyKeyForPathname(`/ui/${seg}/`)).not.toBeNull();
}
// The skills alias resolves to the "skills" sidebar key, not "claude-code-plugins".
expect(legacyKeyForPathname("/ui/skills")).toBe("skills");
expect(legacyKeyForPathname("/ui/budgets")).toBe("budgets");
});
});

View file

@ -12,6 +12,27 @@ export const MIGRATED_PAGES: Record<string, string> = {
api_ref: "api-reference",
// Legacy alias: older bookmarks used the hyphenated ?page=api-reference form.
"api-reference": "api-reference",
budgets: "budgets",
caching: "caching",
"cost-tracking": "cost-tracking",
guardrails: "guardrails",
"guardrails-monitor": "guardrails-monitor",
logs: "logs",
"mcp-servers": "mcp-servers",
memory: "memory",
policies: "policies",
projects: "projects",
prompts: "prompts",
"search-tools": "search-tools",
skills: "skills",
"claude-code-plugins": "skills",
"tag-management": "tag-management",
"tool-policies": "tool-policies",
"transform-request": "transform-request",
"ui-theme": "ui-theme",
"vector-stores": "vector-stores",
workflows: "workflows",
"access-groups": "access-groups",
};
function uiBase(): string {