@burdenoff/microfe-devportal 2026.1108.2 → 2026.1109.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devportal/DevportalRoot.js +68 -61
- package/dist/devportal/DevportalRoot.js.map +1 -1
- package/dist/devportal/DevportalRoutes.js +137 -133
- package/dist/devportal/DevportalRoutes.js.map +1 -1
- package/dist/devportal/components/SectionNav.js.map +1 -1
- package/dist/devportal/pages/ApplicationVersionsPage.js +229 -217
- package/dist/devportal/pages/ApplicationVersionsPage.js.map +1 -1
- package/dist/devportal/pages/NodeLifecyclePage.js +2 -0
- package/dist/devportal/pages/NodeLifecyclePage.js.map +1 -1
- package/dist/devportal/pages/TimeCampusAppletsPage.d.ts +2 -0
- package/dist/devportal/pages/TimeCampusAppletsPage.js +490 -0
- package/dist/devportal/pages/TimeCampusAppletsPage.js.map +1 -0
- package/dist/devportal/utils/constants.js +5 -1
- package/dist/devportal/utils/constants.js.map +1 -1
- package/dist/devportal/utils/timecampusApplets.d.ts +98 -0
- package/dist/devportal/utils/timecampusApplets.js +130 -0
- package/dist/devportal/utils/timecampusApplets.js.map +1 -0
- package/dist/generated/global-operations.d.ts +46 -5
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.d.ts +3901 -638
- package/dist/generated/global-types.js.map +1 -1
- package/dist/generated/wspace-operations.d.ts +678 -2
- package/dist/generated/wspace-operations.js +203 -4
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.d.ts +166197 -34713
- package/dist/translations/en.d.ts +46 -0
- package/dist/translations/en.js +47 -1
- package/dist/translations/en.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SectionNav.js","names":["OrgSwitcher","useDevportal","isSeller","useIsSellerSection","currentOrgId","activePhysicalOrgId","activeOrgId","setCurrentOrgId","setActivePhysicalOrgId","setActiveOrgId","tr","useTr","open","setOpen","useState","ref","useRef","useGetMyOrganizationsQuery","orgs","data","org","useEffect","handler","label","o","createOrgPath","orgSectionPath","v","cn","Building2","ChevronDown","Check","navigate","basePath","Plus"],"sources":["../../../src/devportal/components/SectionNav.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useLocation } from \"react-router\";\nimport {\n LayoutDashboard,\n AppWindow,\n Building2,\n Share2,\n DollarSign,\n BookOpen,\n Library,\n User,\n Settings,\n ChevronDown,\n Check,\n Plus,\n} from \"lucide-react\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport {\n useIsSellerSection,\n orgSectionPath,\n} from \"../hooks/useIsSellerSection\";\nimport {\n useGetMyOrganizationsQuery,\n type GetMyOrganizationsQuery,\n} from \"../hooks/useGraphQL\";\nimport { cn } from \"../utils/cn\";\nimport { useTr } from \"../utils/i18n\";\n\ntype OrgItem = GetMyOrganizationsQuery[\"myDeveloperOrganizations\"][number];\n\ninterface SectionDef {\n path: string;\n i18nKey: string;\n fallback: string;\n icon: typeof LayoutDashboard;\n /** Additional sub-paths that should mark this section as active. */\n matchPrefixes?: string[];\n}\n\nconst SECTIONS: ReadonlyArray<SectionDef> = [\n {\n path: \"\",\n i18nKey: \"devportal.nav.dashboard\",\n fallback: \"Dashboard\",\n icon: LayoutDashboard,\n },\n {\n path: \"apps\",\n i18nKey: \"devportal.nav.apps\",\n fallback: \"Apps\",\n icon: AppWindow,\n },\n {\n path: \"organizations\",\n i18nKey: \"devportal.nav.organizations\",\n fallback: \"Organizations\",\n icon: Building2,\n },\n {\n path: \"delegations\",\n i18nKey: \"devportal.nav.delegations\",\n fallback: \"Delegations\",\n icon: Share2,\n },\n {\n path: \"revenue\",\n i18nKey: \"devportal.nav.revenue\",\n fallback: \"Revenue\",\n icon: DollarSign,\n },\n {\n path: \"docs\",\n i18nKey: \"devportal.nav.docs\",\n fallback: \"Docs\",\n icon: BookOpen,\n },\n {\n path: \"resources\",\n i18nKey: \"devportal.nav.resources\",\n fallback: \"Resources\",\n icon: Library,\n },\n {\n path: \"publisher/profile\",\n i18nKey: \"devportal.nav.publisher\",\n fallback: \"Publisher\",\n icon: User,\n matchPrefixes: [\"publisher\"],\n },\n {\n path: \"settings\",\n i18nKey: \"devportal.nav.settings\",\n fallback: \"Settings\",\n icon: Settings,\n },\n];\n\nfunction joinBaseAndPath(basePath: string | undefined, path: string): string {\n const base = (basePath ?? \"\").replace(/\\/$/, \"\");\n if (!path) return base || \"/\";\n const suffix = path.startsWith(\"/\") ? path : `/${path}`;\n return `${base}${suffix}`;\n}\n\nfunction isSectionActive(\n pathname: string,\n basePath: string | undefined,\n section: SectionDef,\n): boolean {\n const sectionUrl = joinBaseAndPath(basePath, section.path);\n // Index/dashboard is active only when exactly matched (or with empty extra).\n if (section.path === \"\") {\n return pathname === sectionUrl || pathname === `${sectionUrl}/`;\n }\n const candidates = [section.path, ...(section.matchPrefixes ?? [])];\n return candidates.some((prefix) => {\n const url = joinBaseAndPath(basePath, prefix);\n return pathname === url || pathname.startsWith(`${url}/`);\n });\n}\n\n/**\n * Org context switcher — can be embedded standalone or inside SectionNav.\n *\n * Shared between the regular devportal and the seller portal: filters the\n * org list to PHYSICAL-kind orgs under /devportal/seller/** and DIGITAL-kind\n * everywhere else, and reads/writes the matching active-org slot\n * (activePhysicalOrgId vs activeOrgId) so this switcher and\n * useSellerOrganization (used by the item-creation flow) always agree on\n * which org is current. See DeveloperOrgKind (BOFF-7285).\n */\nexport const OrgSwitcher: FC = () => {\n const {\n activeOrgId,\n setActiveOrgId,\n activePhysicalOrgId,\n setActivePhysicalOrgId,\n navigate,\n basePath,\n } = useDevportal();\n const isSeller = useIsSellerSection();\n const currentOrgId = isSeller ? activePhysicalOrgId : activeOrgId;\n const setCurrentOrgId = isSeller ? setActivePhysicalOrgId : setActiveOrgId;\n const tr = useTr();\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n const { data } = useGetMyOrganizationsQuery({ fetchPolicy: \"cache-first\" });\n const allOrgs: OrgItem[] = data?.myDeveloperOrganizations ?? [];\n const orgs = allOrgs.filter((org) =>\n isSeller ? org.kind === \"PHYSICAL\" : org.kind !== \"PHYSICAL\",\n );\n\n // Auto-select when there's exactly one matching org and nothing is stored yet\n useEffect(() => {\n if (!currentOrgId && orgs.length === 1 && orgs[0]?.id) {\n setCurrentOrgId(orgs[0].id);\n }\n }, [orgs, currentOrgId, setCurrentOrgId]);\n\n // Close on outside click\n useEffect(() => {\n const handler = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n if (open) document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [open]);\n\n const activeOrg = orgs.find((o) => o.id === currentOrgId);\n const label =\n activeOrg?.name ??\n (orgs.length === 0\n ? tr(\"devportal.nav.noOrg\", \"No organization\")\n : tr(\"devportal.nav.selectOrg\", \"Select org\"));\n\n const createOrgPath = orgSectionPath(isSeller, \"new\");\n\n return (\n <div ref={ref} className=\"relative flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors\",\n \"bg-bg-surface text-text-primary border-border-subtle hover:bg-accent\",\n )}\n aria-label={tr(\"devportal.nav.switchOrg\", \"Switch organization\")}\n >\n <Building2 size={13} className=\"text-primary flex-shrink-0\" />\n <span className=\"max-w-[140px] truncate\">{label}</span>\n <ChevronDown\n size={13}\n className={cn(\"transition-transform\", open && \"rotate-180\")}\n />\n </button>\n\n {open && (\n <div className=\"absolute right-0 top-full z-50 mt-1 min-w-[200px] rounded-md border border-border-subtle bg-popover shadow-lg py-1\">\n {orgs.map((org) => (\n <button\n key={org.id}\n type=\"button\"\n onClick={() => {\n setCurrentOrgId(org.id);\n setOpen(false);\n }}\n className={cn(\n \"flex w-full items-center gap-2 px-3 py-2 text-sm text-left transition-colors hover:bg-accent\",\n org.id === currentOrgId\n ? \"text-primary font-medium\"\n : \"text-text-primary\",\n )}\n >\n <Check\n size={13}\n className={cn(\n \"flex-shrink-0\",\n org.id === currentOrgId\n ? \"opacity-100 text-primary\"\n : \"opacity-0\",\n )}\n />\n <span className=\"truncate\">{org.name}</span>\n </button>\n ))}\n {orgs.length > 0 && (\n <div className=\"my-1 border-t border-border-subtle\" />\n )}\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n navigate?.(`${basePath ?? \"\"}${createOrgPath}`);\n }}\n className=\"flex w-full items-center gap-2 px-3 py-2 text-sm text-left text-primary transition-colors hover:bg-accent\"\n >\n <Plus size={13} className=\"flex-shrink-0\" />\n <span className=\"truncate\">\n {tr(\"devportal.nav.newOrganization\", \"New organization\")}\n </span>\n </button>\n </div>\n )}\n </div>\n );\n};\n\n/**\n * Horizontal section nav rendered above the page content on every devportal\n * route. Gives developers a one-click jump between all top-level sections so\n * routes like OAuth, Delegations, Analytics, Revenue, Docs, Resources,\n * Publisher Profile, and Settings are reachable without typing URLs.\n *\n * Renders nothing when `basePath` is undefined (standalone/dev mode) so it\n * doesn't conflict with whatever harness wraps the MFE there.\n */\nexport const SectionNav: FC = () => {\n const { navigate, basePath } = useDevportal();\n const { pathname } = useLocation();\n const tr = useTr();\n\n if (!basePath || !navigate) return null;\n\n const handleClick = (path: string) => {\n navigate(joinBaseAndPath(basePath, path));\n };\n\n return (\n <nav\n aria-label={tr(\"devportal.nav.sections\", \"Developer portal sections\")}\n className=\"border-b border-border-subtle bg-bg-surface/40 px-4 py-2\"\n data-testid=\"devportal-section-nav\"\n >\n <div className=\"flex items-center gap-2\">\n <ul className=\"flex flex-1 flex-wrap gap-1\">\n {SECTIONS.map((section) => {\n const active = isSectionActive(pathname, basePath, section);\n const Icon = section.icon;\n return (\n <li key={section.path}>\n <button\n type=\"button\"\n onClick={() => handleClick(section.path)}\n aria-current={active ? \"page\" : undefined}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors\",\n active\n ? \"bg-action-primary-bg text-action-primary-text\"\n : \"text-text-secondary hover:bg-accent hover:text-text-primary\",\n )}\n >\n <Icon size={14} />\n <span>{tr(section.i18nKey, section.fallback)}</span>\n </button>\n </li>\n );\n })}\n </ul>\n <OrgSwitcher />\n </div>\n </nav>\n );\n};\n\n// Exported for unit tests that want to assert against the canonical list.\nexport const __SECTION_NAV_SECTIONS = SECTIONS;\n"],"mappings":";;;;;;;;;;;AAoIA,IAAaA,UAAwB;CACnC,IAAM,EACJ,gBACA,mBACA,wBACA,2BACA,aACA,gBACEC,EAAa,GACXC,IAAWC,EAAmB,GAC9BC,IAAeF,IAAWG,IAAsBC,GAChDC,IAAkBL,IAAWM,IAAyBC,GACtDC,IAAKC,EAAM,GACX,CAACC,GAAMC,KAAWC,EAAS,EAAK,GAChCC,IAAMC,EAAuB,IAAI,GAEjC,EAAE,YAASC,EAA2B,EAAE,aAAa,cAAc,CAAC,GAEpEC,KADqBC,GAAM,4BAA4B,CAAC,EAAA,CACzC,QAAQ,MAC3BjB,IAAWkB,EAAI,SAAS,aAAaA,EAAI,SAAS,UACpD;CAUA,AAPAC,QAAgB;EACd,AAAI,CAACjB,KAAgBc,EAAK,WAAW,KAAKA,EAAK,EAAE,EAAE,MACjDX,EAAgBW,EAAK,EAAE,CAAC,EAAE;CAE9B,GAAG;EAACA;EAAMd;EAAcG;CAAe,CAAC,GAGxCc,QAAgB;EACd,IAAMC,KAAW,MAAkB;GACjC,AAAIP,EAAI,WAAW,CAACA,EAAI,QAAQ,SAAS,EAAE,MAAc,KACvDF,EAAQ,EAAK;EAEjB;EAEA,OADID,KAAM,SAAS,iBAAiB,aAAaU,CAAO,SAC3C,SAAS,oBAAoB,aAAaA,CAAO;CAChE,GAAG,CAACV,CAAI,CAAC;CAGT,IAAMW,IADYL,EAAK,MAAM,MAAMM,EAAE,OAAOpB,CAE1C,CAAA,EAAW,SACVc,EAAK,WAAW,IACbR,EAAG,uBAAuB,iBAAiB,IAC3CA,EAAG,2BAA2B,YAAY,IAE1Ce,IAAgBC,EAAexB,GAAU,KAAK;CAEpD,OACE,kBAAC,OAAD;EAAU;EAAK,WAAU;EAAzB,UAAA,CACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAeW,GAAS,MAAM,CAACc,CAAC;GAChC,WAAWC,EACT,wGACA,sEACF;GACA,cAAYlB,EAAG,2BAA2B,qBAAqB;GAPjE,UAAA;IASE,kBAACmB,GAAD;KAAW,MAAM;KAAI,WAAU;IAA8B,CAAA;IAC7D,kBAAC,QAAD;KAAM,WAAU;KAA0B,UAAAN;IAAY,CAAA;IACtD,kBAACO,GAAD;KACE,MAAM;KACN,WAAWF,EAAG,wBAAwBhB,KAAQ,YAAY;IAC3D,CAAA;GACK;EAEP,CAAA,GAAAA,KACC,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACGM,EAAK,KAAK,MACT,kBAAC,UAAD;KAEE,MAAK;KACL,eAAe;MAEb,AADAX,EAAgBa,EAAI,EAAE,GACtBP,EAAQ,EAAK;KACf;KACA,WAAWe,EACT,gGACAR,EAAI,OAAOhB,IACP,6BACA,mBACN;KAZF,UAAA,CAcE,kBAAC2B,GAAD;MACE,MAAM;MACN,WAAWH,EACT,iBACAR,EAAI,OAAOhB,IACP,6BACA,WACN;KACD,CAAA,GACD,kBAAC,QAAD;MAAM,WAAU;MAAY,UAAAgB,EAAI;KAAW,CAAA,CACrC;IAvBD,GAAAA,EAAI,EAuBH,CACT;IACAF,EAAK,SAAS,KACb,kBAAC,OAAD,EAAK,WAAU,qCAAsC,CAAA;IAEvD,kBAAC,UAAD;KACE,MAAK;KACL,eAAe;MAEb,AADAL,EAAQ,EAAK,GACbmB,IAAW,GAAGC,KAAY,KAAKR,GAAe;KAChD;KACA,WAAU;KANZ,UAAA,CAQE,kBAACS,GAAD;MAAM,MAAM;MAAI,WAAU;KAAiB,CAAA,GAC3C,kBAAC,QAAD;MAAM,WAAU;MACb,UAAAxB,EAAG,iCAAiC,kBAAkB;KACnD,CAAA,CACA;;GACL;EAEJ,CAAA,CAAA;;AAET"}
|
|
1
|
+
{"version":3,"file":"SectionNav.js","names":["OrgSwitcher","useDevportal","isSeller","useIsSellerSection","currentOrgId","activePhysicalOrgId","activeOrgId","setCurrentOrgId","setActivePhysicalOrgId","setActiveOrgId","tr","useTr","open","setOpen","useState","ref","useRef","useGetMyOrganizationsQuery","orgs","data","org","useEffect","handler","label","o","createOrgPath","orgSectionPath","v","cn","Building2","ChevronDown","Check","navigate","basePath","Plus"],"sources":["../../../src/devportal/components/SectionNav.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useLocation } from \"react-router\";\nimport {\n LayoutDashboard,\n AppWindow,\n Blocks,\n Building2,\n Share2,\n DollarSign,\n BookOpen,\n Library,\n User,\n Settings,\n ChevronDown,\n Check,\n Plus,\n} from \"lucide-react\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport {\n useIsSellerSection,\n orgSectionPath,\n} from \"../hooks/useIsSellerSection\";\nimport {\n useGetMyOrganizationsQuery,\n type GetMyOrganizationsQuery,\n} from \"../hooks/useGraphQL\";\nimport { cn } from \"../utils/cn\";\nimport { useTr } from \"../utils/i18n\";\n\ntype OrgItem = GetMyOrganizationsQuery[\"myDeveloperOrganizations\"][number];\n\ninterface SectionDef {\n path: string;\n i18nKey: string;\n fallback: string;\n icon: typeof LayoutDashboard;\n /** Additional sub-paths that should mark this section as active. */\n matchPrefixes?: string[];\n}\n\nconst SECTIONS: ReadonlyArray<SectionDef> = [\n {\n path: \"\",\n i18nKey: \"devportal.nav.dashboard\",\n fallback: \"Dashboard\",\n icon: LayoutDashboard,\n },\n {\n path: \"apps\",\n i18nKey: \"devportal.nav.apps\",\n fallback: \"Apps\",\n icon: AppWindow,\n },\n {\n path: \"timecampus-applets\",\n i18nKey: \"devportal.nav.timecampusApplets\",\n fallback: \"TimeCampus Applets\",\n icon: Blocks,\n },\n {\n path: \"organizations\",\n i18nKey: \"devportal.nav.organizations\",\n fallback: \"Organizations\",\n icon: Building2,\n },\n {\n path: \"delegations\",\n i18nKey: \"devportal.nav.delegations\",\n fallback: \"Delegations\",\n icon: Share2,\n },\n {\n path: \"revenue\",\n i18nKey: \"devportal.nav.revenue\",\n fallback: \"Revenue\",\n icon: DollarSign,\n },\n {\n path: \"docs\",\n i18nKey: \"devportal.nav.docs\",\n fallback: \"Docs\",\n icon: BookOpen,\n },\n {\n path: \"resources\",\n i18nKey: \"devportal.nav.resources\",\n fallback: \"Resources\",\n icon: Library,\n },\n {\n path: \"publisher/profile\",\n i18nKey: \"devportal.nav.publisher\",\n fallback: \"Publisher\",\n icon: User,\n matchPrefixes: [\"publisher\"],\n },\n {\n path: \"settings\",\n i18nKey: \"devportal.nav.settings\",\n fallback: \"Settings\",\n icon: Settings,\n },\n];\n\nfunction joinBaseAndPath(basePath: string | undefined, path: string): string {\n const base = (basePath ?? \"\").replace(/\\/$/, \"\");\n if (!path) return base || \"/\";\n const suffix = path.startsWith(\"/\") ? path : `/${path}`;\n return `${base}${suffix}`;\n}\n\nfunction isSectionActive(\n pathname: string,\n basePath: string | undefined,\n section: SectionDef,\n): boolean {\n const sectionUrl = joinBaseAndPath(basePath, section.path);\n // Index/dashboard is active only when exactly matched (or with empty extra).\n if (section.path === \"\") {\n return pathname === sectionUrl || pathname === `${sectionUrl}/`;\n }\n const candidates = [section.path, ...(section.matchPrefixes ?? [])];\n return candidates.some((prefix) => {\n const url = joinBaseAndPath(basePath, prefix);\n return pathname === url || pathname.startsWith(`${url}/`);\n });\n}\n\n/**\n * Org context switcher — can be embedded standalone or inside SectionNav.\n *\n * Shared between the regular devportal and the seller portal: filters the\n * org list to PHYSICAL-kind orgs under /devportal/seller/** and DIGITAL-kind\n * everywhere else, and reads/writes the matching active-org slot\n * (activePhysicalOrgId vs activeOrgId) so this switcher and\n * useSellerOrganization (used by the item-creation flow) always agree on\n * which org is current. See DeveloperOrgKind (BOFF-7285).\n */\nexport const OrgSwitcher: FC = () => {\n const {\n activeOrgId,\n setActiveOrgId,\n activePhysicalOrgId,\n setActivePhysicalOrgId,\n navigate,\n basePath,\n } = useDevportal();\n const isSeller = useIsSellerSection();\n const currentOrgId = isSeller ? activePhysicalOrgId : activeOrgId;\n const setCurrentOrgId = isSeller ? setActivePhysicalOrgId : setActiveOrgId;\n const tr = useTr();\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n const { data } = useGetMyOrganizationsQuery({ fetchPolicy: \"cache-first\" });\n const allOrgs: OrgItem[] = data?.myDeveloperOrganizations ?? [];\n const orgs = allOrgs.filter((org) =>\n isSeller ? org.kind === \"PHYSICAL\" : org.kind !== \"PHYSICAL\",\n );\n\n // Auto-select when there's exactly one matching org and nothing is stored yet\n useEffect(() => {\n if (!currentOrgId && orgs.length === 1 && orgs[0]?.id) {\n setCurrentOrgId(orgs[0].id);\n }\n }, [orgs, currentOrgId, setCurrentOrgId]);\n\n // Close on outside click\n useEffect(() => {\n const handler = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n if (open) document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [open]);\n\n const activeOrg = orgs.find((o) => o.id === currentOrgId);\n const label =\n activeOrg?.name ??\n (orgs.length === 0\n ? tr(\"devportal.nav.noOrg\", \"No organization\")\n : tr(\"devportal.nav.selectOrg\", \"Select org\"));\n\n const createOrgPath = orgSectionPath(isSeller, \"new\");\n\n return (\n <div ref={ref} className=\"relative flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors\",\n \"bg-bg-surface text-text-primary border-border-subtle hover:bg-accent\",\n )}\n aria-label={tr(\"devportal.nav.switchOrg\", \"Switch organization\")}\n >\n <Building2 size={13} className=\"text-primary flex-shrink-0\" />\n <span className=\"max-w-[140px] truncate\">{label}</span>\n <ChevronDown\n size={13}\n className={cn(\"transition-transform\", open && \"rotate-180\")}\n />\n </button>\n\n {open && (\n <div className=\"absolute right-0 top-full z-50 mt-1 min-w-[200px] rounded-md border border-border-subtle bg-popover shadow-lg py-1\">\n {orgs.map((org) => (\n <button\n key={org.id}\n type=\"button\"\n onClick={() => {\n setCurrentOrgId(org.id);\n setOpen(false);\n }}\n className={cn(\n \"flex w-full items-center gap-2 px-3 py-2 text-sm text-left transition-colors hover:bg-accent\",\n org.id === currentOrgId\n ? \"text-primary font-medium\"\n : \"text-text-primary\",\n )}\n >\n <Check\n size={13}\n className={cn(\n \"flex-shrink-0\",\n org.id === currentOrgId\n ? \"opacity-100 text-primary\"\n : \"opacity-0\",\n )}\n />\n <span className=\"truncate\">{org.name}</span>\n </button>\n ))}\n {orgs.length > 0 && (\n <div className=\"my-1 border-t border-border-subtle\" />\n )}\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n navigate?.(`${basePath ?? \"\"}${createOrgPath}`);\n }}\n className=\"flex w-full items-center gap-2 px-3 py-2 text-sm text-left text-primary transition-colors hover:bg-accent\"\n >\n <Plus size={13} className=\"flex-shrink-0\" />\n <span className=\"truncate\">\n {tr(\"devportal.nav.newOrganization\", \"New organization\")}\n </span>\n </button>\n </div>\n )}\n </div>\n );\n};\n\n/**\n * Horizontal section nav rendered above the page content on every devportal\n * route. Gives developers a one-click jump between all top-level sections so\n * routes like OAuth, Delegations, Analytics, Revenue, Docs, Resources,\n * Publisher Profile, and Settings are reachable without typing URLs.\n *\n * Renders nothing when `basePath` is undefined (standalone/dev mode) so it\n * doesn't conflict with whatever harness wraps the MFE there.\n */\nexport const SectionNav: FC = () => {\n const { navigate, basePath } = useDevportal();\n const { pathname } = useLocation();\n const tr = useTr();\n\n if (!basePath || !navigate) return null;\n\n const handleClick = (path: string) => {\n navigate(joinBaseAndPath(basePath, path));\n };\n\n return (\n <nav\n aria-label={tr(\"devportal.nav.sections\", \"Developer portal sections\")}\n className=\"border-b border-border-subtle bg-bg-surface/40 px-4 py-2\"\n data-testid=\"devportal-section-nav\"\n >\n <div className=\"flex items-center gap-2\">\n <ul className=\"flex flex-1 flex-wrap gap-1\">\n {SECTIONS.map((section) => {\n const active = isSectionActive(pathname, basePath, section);\n const Icon = section.icon;\n return (\n <li key={section.path}>\n <button\n type=\"button\"\n onClick={() => handleClick(section.path)}\n aria-current={active ? \"page\" : undefined}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors\",\n active\n ? \"bg-action-primary-bg text-action-primary-text\"\n : \"text-text-secondary hover:bg-accent hover:text-text-primary\",\n )}\n >\n <Icon size={14} />\n <span>{tr(section.i18nKey, section.fallback)}</span>\n </button>\n </li>\n );\n })}\n </ul>\n <OrgSwitcher />\n </div>\n </nav>\n );\n};\n\n// Exported for unit tests that want to assert against the canonical list.\nexport const __SECTION_NAV_SECTIONS = SECTIONS;\n"],"mappings":";;;;;;;;;;;AA2IA,IAAaA,UAAwB;CACnC,IAAM,EACJ,gBACA,mBACA,wBACA,2BACA,aACA,gBACEC,EAAa,GACXC,IAAWC,EAAmB,GAC9BC,IAAeF,IAAWG,IAAsBC,GAChDC,IAAkBL,IAAWM,IAAyBC,GACtDC,IAAKC,EAAM,GACX,CAACC,GAAMC,KAAWC,EAAS,EAAK,GAChCC,IAAMC,EAAuB,IAAI,GAEjC,EAAE,YAASC,EAA2B,EAAE,aAAa,cAAc,CAAC,GAEpEC,KADqBC,GAAM,4BAA4B,CAAC,EAAA,CACzC,QAAQ,MAC3BjB,IAAWkB,EAAI,SAAS,aAAaA,EAAI,SAAS,UACpD;CAUA,AAPAC,QAAgB;EACd,AAAI,CAACjB,KAAgBc,EAAK,WAAW,KAAKA,EAAK,EAAE,EAAE,MACjDX,EAAgBW,EAAK,EAAE,CAAC,EAAE;CAE9B,GAAG;EAACA;EAAMd;EAAcG;CAAe,CAAC,GAGxCc,QAAgB;EACd,IAAMC,KAAW,MAAkB;GACjC,AAAIP,EAAI,WAAW,CAACA,EAAI,QAAQ,SAAS,EAAE,MAAc,KACvDF,EAAQ,EAAK;EAEjB;EAEA,OADID,KAAM,SAAS,iBAAiB,aAAaU,CAAO,SAC3C,SAAS,oBAAoB,aAAaA,CAAO;CAChE,GAAG,CAACV,CAAI,CAAC;CAGT,IAAMW,IADYL,EAAK,MAAM,MAAMM,EAAE,OAAOpB,CAE1C,CAAA,EAAW,SACVc,EAAK,WAAW,IACbR,EAAG,uBAAuB,iBAAiB,IAC3CA,EAAG,2BAA2B,YAAY,IAE1Ce,IAAgBC,EAAexB,GAAU,KAAK;CAEpD,OACE,kBAAC,OAAD;EAAU;EAAK,WAAU;EAAzB,UAAA,CACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAeW,GAAS,MAAM,CAACc,CAAC;GAChC,WAAWC,EACT,wGACA,sEACF;GACA,cAAYlB,EAAG,2BAA2B,qBAAqB;GAPjE,UAAA;IASE,kBAACmB,GAAD;KAAW,MAAM;KAAI,WAAU;IAA8B,CAAA;IAC7D,kBAAC,QAAD;KAAM,WAAU;KAA0B,UAAAN;IAAY,CAAA;IACtD,kBAACO,GAAD;KACE,MAAM;KACN,WAAWF,EAAG,wBAAwBhB,KAAQ,YAAY;IAC3D,CAAA;GACK;EAEP,CAAA,GAAAA,KACC,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACGM,EAAK,KAAK,MACT,kBAAC,UAAD;KAEE,MAAK;KACL,eAAe;MAEb,AADAX,EAAgBa,EAAI,EAAE,GACtBP,EAAQ,EAAK;KACf;KACA,WAAWe,EACT,gGACAR,EAAI,OAAOhB,IACP,6BACA,mBACN;KAZF,UAAA,CAcE,kBAAC2B,GAAD;MACE,MAAM;MACN,WAAWH,EACT,iBACAR,EAAI,OAAOhB,IACP,6BACA,WACN;KACD,CAAA,GACD,kBAAC,QAAD;MAAM,WAAU;MAAY,UAAAgB,EAAI;KAAW,CAAA,CACrC;IAvBD,GAAAA,EAAI,EAuBH,CACT;IACAF,EAAK,SAAS,KACb,kBAAC,OAAD,EAAK,WAAU,qCAAsC,CAAA;IAEvD,kBAAC,UAAD;KACE,MAAK;KACL,eAAe;MAEb,AADAL,EAAQ,EAAK,GACbmB,IAAW,GAAGC,KAAY,KAAKR,GAAe;KAChD;KACA,WAAU;KANZ,UAAA,CAQE,kBAACS,GAAD;MAAM,MAAM;MAAI,WAAU;KAAiB,CAAA,GAC3C,kBAAC,QAAD;MAAM,WAAU;MACb,UAAAxB,EAAG,iCAAiC,kBAAkB;KACnD,CAAA,CACA;;GACL;EAEJ,CAAA,CAAA;;AAET"}
|