import type { ReactNode } from "react"; import { TFunction, i18n } from "i18next"; import { NavigateFunction } from "react-router"; import { AdminSidebar } from "~/application/sidebar/AdminSidebar"; import { SideBarItem } from "~/application/sidebar/SidebarItem"; import { getUserHasPermission } from "~/utils/helpers/PermissionsHelper"; import { AppLoaderData } from "~/utils/data/useAppData"; import { AppRootData } from "~/utils/data/useRootData"; import { getDocCommands } from "~/utils/services/docsService"; import { appPath } from "~/utils/app/CanonicalUrlUtils"; import { absoluteAdminPath, absoluteAppRootPath, isAbsoluteHttpUrl, type PublicDomainRoutingConfig } from "~/utils/app/CrossHostUrls"; import { filterCommandActions } from "~/services/search/commandSearch"; import { translateLabel } from "~/utils/i18n/translateLabel"; // Registry marker: services/command-registry paths are app-relative for package verification. export type CommandRegistryLayout = "app" | "admin" | "docs"; export type CommandActionScope = { layout: CommandRegistryLayout; tenantSlug?: string; platform?: boolean; adminOnly?: boolean; featureFlag?: string; permission?: string; }; export type CommandAction = { id: string; name: string; subtitle?: string; keywords?: string; shortcut?: string[]; icon?: ReactNode; perform?: (currentAction: CommandAction) => void; }; export type ScopedCommandAction = CommandAction & { scope: CommandActionScope; }; export type CommandRegistryContext = { layout: CommandRegistryLayout; t: TFunction; i18n?: Pick; navigate: NavigateFunction; submitLogout?: () => void; appData?: AppLoaderData; rootData?: AppRootData; }; export function getCommandRegistryActions(context: CommandRegistryContext): ScopedCommandAction[] { const actions = getUnfilteredCommandRegistryActions(context); return filterCommandActions({ actions, query: "", layout: context.layout, tenantSlug: context.appData?.currentTenant?.slug, appData: context.appData, rootData: context.rootData, }); } function getUnfilteredCommandRegistryActions(context: CommandRegistryContext): ScopedCommandAction[] { if (context.layout === "app") { return getAppCommandActions(context); } if (context.layout === "admin") { return getAdminCommandActions(context); } return getDocsCommandActions(context); } function getAppCommandActions({ t, i18n, navigate, submitLogout, appData, rootData }: CommandRegistryContext): ScopedCommandAction[] { if (!appData || !rootData) { return []; } const tenantSlug = appData.currentTenant.slug; const tenantPath = (path?: string) => appPath({ tenantSlug, path, mode: "host-aware" }); const tenantScope = (metadata?: Omit): CommandActionScope => ({ layout: "app", tenantSlug, ...metadata }); const actions: ScopedCommandAction[] = [ { id: "dashboard", shortcut: [], name: t("app.sidebar.dashboard"), keywords: "", scope: tenantScope(), perform: () => navigate(tenantPath("dashboard")), }, ]; appData.entities .filter((entity) => entity.showInSidebar) .forEach((entity) => { const permission = typeof entity.name === "string" && entity.name.trim() ? `entity.${entity.name}.view` : undefined; actions.push({ id: "entity-" + entity.name, name: translateLabel(t, i18n, entity.titlePlural), scope: tenantScope({ permission }), perform: () => navigate(tenantPath(entity.slug)), }); }); actions.push({ id: "profile", shortcut: [], name: t("app.commands.profile.title"), subtitle: t("app.sidebar.settings"), keywords: t("app.commands.profile.description"), scope: tenantScope(), perform: () => navigate(tenantPath("settings/profile")), }); pushPermissionAction(actions, appData, "app.settings.members.view", { id: "members", name: t("app.navbar.members"), shortcut: [], keywords: "", subtitle: t("app.sidebar.settings"), scope: tenantScope({ permission: "app.settings.members.view" }), perform: () => navigate(tenantPath("settings/members")), }); pushPermissionAction(actions, appData, "app.settings.subscription.view", { id: "subscription", name: t("app.navbar.subscription"), shortcut: [], keywords: "", subtitle: t("app.sidebar.settings"), scope: tenantScope({ permission: "app.settings.subscription.view" }), perform: () => navigate(tenantPath("settings/subscription")), }); pushPermissionAction(actions, appData, "app.settings.account.view", { id: "account", name: t("app.navbar.tenant"), shortcut: [], keywords: "", subtitle: t("app.sidebar.settings"), scope: tenantScope({ permission: "app.settings.account.view" }), perform: () => navigate(tenantPath("settings/account")), }); if (rootData.appConfiguration.app.features.tenantApiKeys) { pushPermissionAction(actions, appData, "app.settings.apiKeys.view", { id: "apiKeys", name: t("models.apiKey.plural"), shortcut: [], keywords: "", subtitle: t("app.sidebar.settings"), scope: tenantScope({ featureFlag: "tenantApiKeys", permission: "app.settings.apiKeys.view" }), perform: () => navigate(tenantPath("settings/api")), }); } if (getUserHasPermission(appData, "app.settings.auditTrails.view")) { actions.push({ id: "auditTrails", name: t("models.log.plural"), shortcut: [], keywords: "", subtitle: t("app.sidebar.settings"), scope: tenantScope({ permission: "app.settings.auditTrails.view" }), perform: () => navigate(tenantPath("settings/logs")), }); actions.push({ id: "events", name: t("models.event.plural"), shortcut: [], keywords: "", subtitle: t("app.sidebar.settings"), scope: tenantScope({ permission: "app.settings.auditTrails.view" }), perform: () => navigate(tenantPath("settings/logs/events")), }); } if (appData.myTenants.length > 0) { actions.push({ id: "switch account", shortcut: [], name: t("app.commands.tenants.switch"), keywords: t("app.commands.tenants.description"), scope: { layout: "app", platform: true }, perform: () => navigateOrAssign(absoluteAppRootPath(rootData.routing), navigate), }); } actions.push({ id: "create account", shortcut: [], name: t("app.commands.tenants.create"), keywords: "", scope: { layout: "app", platform: true }, perform: () => navigateOrAssign(absolutePublicAppPath(rootData.routing, "new-account"), navigate), }); if (appData.user?.admin) { actions.push({ id: "switch to admin", shortcut: [], name: t("admin.switchToAdmin"), keywords: "", scope: { layout: "app", platform: true, adminOnly: true }, perform: () => navigateOrAssign(absoluteAdminPath(rootData.routing, "dashboard"), navigate), }); } actions.push({ id: "logout", shortcut: [], name: t("app.commands.profile.logout"), scope: { layout: "app", platform: true }, perform: () => submitLogout?.(), }); return actions; } function pushPermissionAction(actions: ScopedCommandAction[], appData: AppLoaderData, permission: string, action: ScopedCommandAction) { if (getUserHasPermission(appData, permission as never)) { actions.push(action); } } function getAdminCommandActions({ t, navigate, submitLogout, rootData }: CommandRegistryContext): ScopedCommandAction[] { if (!rootData) { return []; } const actions = getSidebarCommandActions(AdminSidebar({ t, appConfiguration: rootData.appConfiguration, routing: rootData.routing }), "admin").map( (action) => ({ ...action, perform(currentAction: CommandAction) { navigateOrAssign(currentAction.id, navigate); }, }) ); actions.push({ id: "create account", shortcut: [], name: t("app.commands.tenants.create"), keywords: "", scope: { layout: "admin", platform: true }, perform: () => navigateOrAssign(absolutePublicAppPath(rootData.routing, "new-account"), navigate), }); actions.push({ id: "logout", shortcut: [], name: t("app.commands.profile.logout"), scope: { layout: "admin", platform: true }, perform: () => submitLogout?.(), }); return actions; } function navigateOrAssign(target: string, navigate: NavigateFunction) { if (isAbsoluteHttpUrl(target) && typeof window !== "undefined") { window.location.assign(target); return; } navigate(target); } function absolutePublicAppPath(routing: PublicDomainRoutingConfig | undefined, path: string) { const appRoot = absoluteAppRootPath(routing); const cleanPath = path.replace(/^\/+/, ""); if (isAbsoluteHttpUrl(appRoot)) { return new URL(cleanPath, appRoot).toString(); } return `/${cleanPath}`; } function getDocsCommandActions({ navigate }: CommandRegistryContext): ScopedCommandAction[] { return getDocCommands().map((action) => ({ ...action, scope: { layout: "docs", platform: true }, perform(currentAction) { navigate(currentAction.id); }, })); } export function getSidebarCommandActions(items: SideBarItem[], layout: CommandRegistryLayout): ScopedCommandAction[] { const commands: ScopedCommandAction[] = []; items.forEach((item) => commands.push(...getCommandsFromSidebarItem(item, layout, [], []))); return commands; } function getCommandsFromSidebarItem(item: SideBarItem, layout: CommandRegistryLayout, commands: ScopedCommandAction[], parent: string[]) { if (item.hidden || item.disabled) { return commands; } const isExpandableOnlyParent = item.isCollapsible && item.items && item.items.length > 0; if (item.path && item.title && !isExpandableOnlyParent) { let description = item.description ?? ""; const parentTitles = parent.filter((value) => value); if (parentTitles.length > 0) { description = parentTitles.join(" / "); } commands.push({ id: item.path, name: item.title, shortcut: [], subtitle: description, scope: { layout, platform: true, adminOnly: layout === "admin", permission: item.permission }, }); } item.items?.forEach((subItem) => getCommandsFromSidebarItem(subItem, layout, commands, [...parent, item.title])); return commands; }