import { useEffect, useMemo } from "react"; import { Link, Navigate, useParams } from "@/lib/router"; import { useQuery } from "@tanstack/react-query"; import { useCompany } from "@/context/CompanyContext"; import { useBreadcrumbs } from "@/context/BreadcrumbContext"; import { pluginsApi } from "@/api/plugins"; import { queryKeys } from "@/lib/queryKeys"; import { PluginSlotMount } from "@/plugins/slots"; import { Button } from "@/components/ui/button"; import { ArrowLeft } from "lucide-react"; import { NotFoundPage } from "./NotFound"; /** * Company-context plugin page. Renders a plugin's `page` slot at * `/:companyPrefix/plugins/:pluginId` when the plugin declares a page slot * and is enabled for that company. * * @see doc/plugins/PLUGIN_SPEC.md §19.2 — Company-Context Routes * @see doc/plugins/PLUGIN_SPEC.md §24.4 — Company-Context Plugin Page */ export function PluginPage() { const { companyPrefix: routeCompanyPrefix, pluginId, pluginRoutePath } = useParams<{ companyPrefix?: string; pluginId?: string; pluginRoutePath?: string; }>(); const { companies, selectedCompanyId } = useCompany(); const { setBreadcrumbs } = useBreadcrumbs(); const routeCompany = useMemo(() => { if (!routeCompanyPrefix) return null; const requested = routeCompanyPrefix.toUpperCase(); return companies.find((c) => c.issuePrefix.toUpperCase() === requested) ?? null; }, [companies, routeCompanyPrefix]); const hasInvalidCompanyPrefix = Boolean(routeCompanyPrefix) && !routeCompany; const resolvedCompanyId = useMemo(() => { if (routeCompany) return routeCompany.id; if (routeCompanyPrefix) return null; return selectedCompanyId ?? null; }, [routeCompany, routeCompanyPrefix, selectedCompanyId]); const companyPrefix = useMemo( () => (resolvedCompanyId ? companies.find((c) => c.id === resolvedCompanyId)?.issuePrefix ?? null : null), [companies, resolvedCompanyId], ); const { data: contributions } = useQuery({ queryKey: queryKeys.plugins.uiContributions, queryFn: () => pluginsApi.listUiContributions(), enabled: !!resolvedCompanyId && (!!pluginId || !!pluginRoutePath), }); const pageSlot = useMemo(() => { if (!contributions) return null; if (pluginId) { const contribution = contributions.find((c) => c.pluginId === pluginId); if (!contribution) return null; const slot = contribution.slots.find((s) => s.type === "page"); if (!slot) return null; return { ...slot, pluginId: contribution.pluginId, pluginKey: contribution.pluginKey, pluginDisplayName: contribution.displayName, pluginVersion: contribution.version, }; } if (!pluginRoutePath) return null; const matches = contributions.flatMap((contribution) => { const slot = contribution.slots.find((entry) => entry.type === "page" && entry.routePath === pluginRoutePath); if (!slot) return []; return [{ ...slot, pluginId: contribution.pluginId, pluginKey: contribution.pluginKey, pluginDisplayName: contribution.displayName, pluginVersion: contribution.version, }]; }); if (matches.length !== 1) return null; return matches[0] ?? null; }, [pluginId, pluginRoutePath, contributions]); const context = useMemo( () => ({ companyId: resolvedCompanyId ?? null, companyPrefix, }), [resolvedCompanyId, companyPrefix], ); useEffect(() => { if (pageSlot) { setBreadcrumbs([ { label: "Plugins", href: "/instance/settings/plugins" }, { label: pageSlot.pluginDisplayName }, ]); } }, [pageSlot, companyPrefix, setBreadcrumbs]); if (!resolvedCompanyId) { if (hasInvalidCompanyPrefix) { return ; } return (

Select a company to view this page.

); } if (!contributions) { return
Loading…
; } if (!pluginId && pluginRoutePath) { const duplicateMatches = contributions.filter((contribution) => contribution.slots.some((slot) => slot.type === "page" && slot.routePath === pluginRoutePath), ); if (duplicateMatches.length > 1) { return (
Multiple plugins declare the route {pluginRoutePath}. Use the plugin-id route until the conflict is resolved.
); } } if (!pageSlot) { if (pluginRoutePath) { return ; } // No page slot: redirect to plugin settings where plugin info is always shown const settingsPath = pluginId ? `/instance/settings/plugins/${pluginId}` : "/instance/settings/plugins"; return ; } return (
); }