@burdenoff/microfe-devportal 2026.1113.1 → 2026.1113.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 +65 -119
- package/dist/devportal/DevportalRoot.js.map +1 -1
- package/dist/devportal/components/AppCard.js +4 -4
- package/dist/devportal/components/DeveloperSettingsHub.d.ts +6 -0
- package/dist/devportal/components/DeveloperSettingsHub.js +59 -0
- package/dist/devportal/components/DeveloperSettingsHub.js.map +1 -0
- package/dist/devportal/components/SectionNav.d.ts +1 -1
- package/dist/devportal/components/SectionNav.js +60 -36
- package/dist/devportal/components/SectionNav.js.map +1 -1
- package/dist/devportal/pages/AnalyticsDashboardPage.js +186 -179
- package/dist/devportal/pages/AnalyticsDashboardPage.js.map +1 -1
- package/dist/devportal/pages/ApiKeysPage.js +113 -111
- package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
- package/dist/devportal/pages/AppLogsPage.js +80 -84
- package/dist/devportal/pages/AppLogsPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationDetailPage.js +529 -504
- package/dist/devportal/pages/ApplicationDetailPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationsPage.js +68 -67
- package/dist/devportal/pages/ApplicationsPage.js.map +1 -1
- package/dist/devportal/pages/CreateApiKeyPage.js +163 -166
- package/dist/devportal/pages/CreateApiKeyPage.js.map +1 -1
- package/dist/devportal/pages/CreateOAuthClientPage.js +134 -130
- package/dist/devportal/pages/CreateOAuthClientPage.js.map +1 -1
- package/dist/devportal/pages/DelegationsPage.js +11 -11
- package/dist/devportal/pages/DocumentationPage.js +100 -94
- package/dist/devportal/pages/DocumentationPage.js.map +1 -1
- package/dist/devportal/pages/EditApplicationPage.js +379 -377
- package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
- package/dist/devportal/pages/InvitationDecisionPage.js +83 -79
- package/dist/devportal/pages/InvitationDecisionPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientDetailPage.js +76 -76
- package/dist/devportal/pages/OAuthClientDetailPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientsPage.js +126 -124
- package/dist/devportal/pages/OAuthClientsPage.js.map +1 -1
- package/dist/devportal/pages/OrganizationDetailPage.js +19 -19
- package/dist/devportal/pages/OrganizationMembersPage.js +223 -204
- package/dist/devportal/pages/OrganizationMembersPage.js.map +1 -1
- package/dist/devportal/pages/ResourcesPage.d.ts +6 -0
- package/dist/devportal/pages/ResourcesPage.js +171 -155
- package/dist/devportal/pages/ResourcesPage.js.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.js +13 -13
- package/dist/devportal/pages/SellerItemsPage.js +4 -4
- package/dist/devportal/pages/SettingsPage.js +330 -315
- package/dist/devportal/pages/SettingsPage.js.map +1 -1
- package/dist/devportal/pages/SetupPage.js +5 -5
- package/dist/devportal/pages/WebhookDetailPage.js +6 -6
- package/dist/devportal/pages/WebhooksPage.js +39 -39
- package/dist/devportal/pages/WebhooksPage.js.map +1 -1
- package/dist/devportal/utils/appDetailTabs.d.ts +73 -0
- package/dist/devportal/utils/appDetailTabs.js +62 -0
- package/dist/devportal/utils/appDetailTabs.js.map +1 -0
- package/dist/devportal/utils/developerNav.d.ts +39 -0
- package/dist/devportal/utils/developerNav.js +304 -0
- package/dist/devportal/utils/developerNav.js.map +1 -0
- package/package.json +2 -2
|
@@ -2,149 +2,95 @@ import { DevportalProvider as e } from "./providers/DevportalProvider.js";
|
|
|
2
2
|
import { useTr as t } from "./utils/i18n.js";
|
|
3
3
|
import { DevportalRoutes as n } from "./DevportalRoutes.js";
|
|
4
4
|
import { setDevportalEventBusBridge as r } from "./events/index.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
5
|
+
import { getMenuEntries as i } from "./utils/developerNav.js";
|
|
6
|
+
import { useEffect as a } from "react";
|
|
7
|
+
import { ErrorBoundary as o } from "@burdenoff/fe-libs/shared/components";
|
|
8
|
+
import { AppWindow as s, BarChart3 as c, Blocks as l, BookOpen as u, Building2 as d, Code2 as f, DollarSign as p, Key as m, LayoutDashboard as h, Settings as g, Shield as _, UserCircle as v, Webhook as y, Wrench as b } from "lucide-react";
|
|
9
|
+
import { jsx as x } from "react/jsx-runtime";
|
|
10
|
+
import { FeatureFlagProvider as S } from "@burdenoff/fe-libs/shared/feature-flags";
|
|
11
|
+
import { useEventBus as C } from "@burdenoff/fe-libs/shared/events";
|
|
12
|
+
import { useHeadingDocumentTitle as w } from "@burdenoff/fe-libs/ui";
|
|
11
13
|
// src/devportal/DevportalRoot.tsx
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
var T = {
|
|
15
|
+
"devportal-dashboard": h,
|
|
16
|
+
"devportal-organizations": d,
|
|
17
|
+
"devportal-apps": s,
|
|
18
|
+
"devportal-timecampus-applets": l,
|
|
19
|
+
"devportal-oauth": _,
|
|
20
|
+
"devportal-api-keys": m,
|
|
21
|
+
"devportal-webhooks": y,
|
|
22
|
+
"devportal-analytics": c,
|
|
23
|
+
"devportal-revenue": p,
|
|
24
|
+
"devportal-docs": u,
|
|
25
|
+
"devportal-resources": b,
|
|
26
|
+
"devportal-settings": g,
|
|
27
|
+
"devportal-publisher": v
|
|
28
|
+
}, E = ({ id: e }) => {
|
|
29
|
+
let t = T[e] ?? f;
|
|
30
|
+
return /* @__PURE__ */ x(t, { size: 16 });
|
|
31
|
+
}, D = (s) => {
|
|
32
|
+
let { registerNavItems: c, registerFooterItems: l, basePath: u = "/" } = s, d = t(), p = s.variant === "fluidgrids";
|
|
33
|
+
w({
|
|
34
|
+
enabled: s.variant === "fluidgrids",
|
|
35
|
+
suffix: "FluidGrids"
|
|
36
|
+
});
|
|
37
|
+
let m = (e, t) => (e.endsWith("/") ? e.slice(0, -1) : e) + (t.startsWith("/") ? t : `/${t}`);
|
|
38
|
+
a(() => {
|
|
39
|
+
if (!c) return;
|
|
16
40
|
let e = {
|
|
17
41
|
id: "devportal",
|
|
18
|
-
label:
|
|
19
|
-
path:
|
|
20
|
-
icon: /* @__PURE__ */
|
|
42
|
+
label: d("devportal.developerPortal", "Developer Portal"),
|
|
43
|
+
path: u,
|
|
44
|
+
icon: /* @__PURE__ */ x(f, { size: 20 }),
|
|
21
45
|
order: 75,
|
|
22
46
|
group: "global",
|
|
23
|
-
children:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
id: "devportal-organizations",
|
|
33
|
-
label: E("devportal.organizations", "Organizations"),
|
|
34
|
-
path: D(T, "organizations"),
|
|
35
|
-
icon: /* @__PURE__ */ y(u, { size: 16 }),
|
|
36
|
-
order: 20
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
id: "devportal-apps",
|
|
40
|
-
label: E("devportal.myApps", "My Apps"),
|
|
41
|
-
path: D(T, "apps"),
|
|
42
|
-
icon: /* @__PURE__ */ y(o, { size: 16 }),
|
|
43
|
-
order: 30
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
id: "devportal-timecampus-applets",
|
|
47
|
-
label: E("devportal.nav.timecampusApplets", "TimeCampus Applets"),
|
|
48
|
-
path: D(T, "timecampus-applets"),
|
|
49
|
-
icon: /* @__PURE__ */ y(c, { size: 16 }),
|
|
50
|
-
order: 35
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
id: "devportal-oauth",
|
|
54
|
-
label: E("devportal.oauthClients", "OAuth Clients"),
|
|
55
|
-
path: D(T, "oauth"),
|
|
56
|
-
icon: /* @__PURE__ */ y(g, { size: 16 }),
|
|
57
|
-
order: 40
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
id: "devportal-api-keys",
|
|
61
|
-
label: E("devportal.apiKeys", "API Keys"),
|
|
62
|
-
path: D(T, "api-keys"),
|
|
63
|
-
icon: /* @__PURE__ */ y(p, { size: 16 }),
|
|
64
|
-
order: 50
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
id: "devportal-webhooks",
|
|
68
|
-
label: E("devportal.webhooks", "Webhooks"),
|
|
69
|
-
path: D(T, "webhooks"),
|
|
70
|
-
icon: /* @__PURE__ */ y(_, { size: 16 }),
|
|
71
|
-
order: 60
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
id: "devportal-analytics",
|
|
75
|
-
label: E("devportal.analytics", "Analytics"),
|
|
76
|
-
path: D(T, "analytics"),
|
|
77
|
-
icon: /* @__PURE__ */ y(s, { size: 16 }),
|
|
78
|
-
order: 70
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
id: "devportal-revenue",
|
|
82
|
-
label: E("devportal.revenue", "Revenue"),
|
|
83
|
-
path: "/billing/earnings",
|
|
84
|
-
icon: /* @__PURE__ */ y(f, { size: 16 }),
|
|
85
|
-
order: 80
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
id: "devportal-docs",
|
|
89
|
-
label: E("devportal.documentation", "Documentation"),
|
|
90
|
-
path: D(T, "docs"),
|
|
91
|
-
icon: /* @__PURE__ */ y(l, { size: 16 }),
|
|
92
|
-
order: 90
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
id: "devportal-resources",
|
|
96
|
-
label: E("devportal.resources", "Resources"),
|
|
97
|
-
path: D(T, "resources"),
|
|
98
|
-
icon: /* @__PURE__ */ y(v, { size: 16 }),
|
|
99
|
-
order: 100
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
id: "devportal-settings",
|
|
103
|
-
label: E("devportal.settings", "Settings"),
|
|
104
|
-
path: D(T, "settings"),
|
|
105
|
-
icon: /* @__PURE__ */ y(h, { size: 16 }),
|
|
106
|
-
order: 110
|
|
107
|
-
}
|
|
108
|
-
]
|
|
47
|
+
children: i(p).map((e) => ({
|
|
48
|
+
id: e.id,
|
|
49
|
+
label: d(e.i18nKey, e.fallback),
|
|
50
|
+
path: e.absolutePath ? e.absolutePath : e.path ? m(u, e.path) : u,
|
|
51
|
+
icon: /* @__PURE__ */ x(E, { id: e.id }),
|
|
52
|
+
order: e.order
|
|
53
|
+
}))
|
|
109
54
|
};
|
|
110
|
-
return
|
|
55
|
+
return c(e);
|
|
111
56
|
}, [
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
57
|
+
c,
|
|
58
|
+
u,
|
|
59
|
+
d,
|
|
60
|
+
p
|
|
61
|
+
]), a(() => {
|
|
62
|
+
if (!l) return;
|
|
117
63
|
let e = [{
|
|
118
64
|
id: "devportal-status",
|
|
119
|
-
label:
|
|
65
|
+
label: d("devportal.developerPortalReady", "Developer Portal Ready"),
|
|
120
66
|
type: "status",
|
|
121
67
|
order: 10,
|
|
122
68
|
section: "left"
|
|
123
69
|
}];
|
|
124
|
-
return
|
|
125
|
-
}, [
|
|
70
|
+
return l(e);
|
|
71
|
+
}, [l, d]), a(() => {
|
|
126
72
|
let e = window.__breadcrumbRegistry;
|
|
127
|
-
if (e) return e.register("oauth",
|
|
73
|
+
if (e) return e.register("oauth", p ? d("devportal.nav.oauthApps", "OAuth apps") : d("devportal.nav.oauth", "OAuth Clients")), e.register("api-keys", d("devportal.nav.apiKeys", "API Keys")), () => {
|
|
128
74
|
e.unregister("oauth"), e.unregister("api-keys");
|
|
129
75
|
};
|
|
130
|
-
}, [
|
|
131
|
-
let
|
|
132
|
-
return
|
|
133
|
-
fallback: /* @__PURE__ */
|
|
76
|
+
}, [d, p]);
|
|
77
|
+
let h = C();
|
|
78
|
+
return a(() => (r(h), () => r(null)), [h]), /* @__PURE__ */ x(o, {
|
|
79
|
+
fallback: /* @__PURE__ */ x("div", {
|
|
134
80
|
className: "p-6 text-center text-text-secondary",
|
|
135
|
-
children:
|
|
81
|
+
children: d("devportal.error.moduleLoadFailed", "Something went wrong loading this module. Please refresh the page.")
|
|
136
82
|
}),
|
|
137
|
-
children: /* @__PURE__ */
|
|
83
|
+
children: /* @__PURE__ */ x(S, {
|
|
138
84
|
workspaceId: null,
|
|
139
85
|
gateway: "global",
|
|
140
86
|
defaultEnabled: !0,
|
|
141
|
-
children: /* @__PURE__ */
|
|
142
|
-
...
|
|
143
|
-
children: /* @__PURE__ */
|
|
87
|
+
children: /* @__PURE__ */ x(e, {
|
|
88
|
+
...s,
|
|
89
|
+
children: /* @__PURE__ */ x(n, {})
|
|
144
90
|
})
|
|
145
91
|
})
|
|
146
92
|
});
|
|
147
93
|
};
|
|
148
|
-
export {
|
|
94
|
+
export { D as DevportalRoot };
|
|
149
95
|
|
|
150
96
|
//# sourceMappingURL=DevportalRoot.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DevportalRoot.js","names":["DevportalRoot","props","tr","useTr","joinPath","base","path","useEffect","registerNavItems","devportalNavItem","basePath","Code2","LayoutDashboard","Building2","AppWindow","Blocks","Shield","Key","Webhook","BarChart3","DollarSign","BookOpen","Wrench","Settings","registerFooterItems","footerItems","reg","sharedBus","useEventBus","setDevportalEventBusBridge","ErrorBoundary","FeatureFlagProvider","DevportalProvider","DevportalRoutes"],"sources":["../../src/devportal/DevportalRoot.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect } from \"react\";\nimport { ErrorBoundary } from \"@burdenoff/fe-libs/shared/components\";\nimport {\n Code2,\n LayoutDashboard,\n Building2,\n AppWindow,\n Blocks,\n BookOpen,\n Settings,\n Webhook,\n Key,\n Shield,\n BarChart3,\n DollarSign,\n Wrench,\n} from \"lucide-react\";\nimport { DevportalProvider } from \"./providers/DevportalProvider\";\nimport { FeatureFlagProvider } from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport { useTr } from \"./utils/i18n\";\nimport type { NavItem, DevportalRootProps } from \"./types\";\nimport { DevportalRoutes } from \"./DevportalRoutes\";\nimport { setDevportalEventBusBridge } from \"./events\";\n\n/**\n * Root component for the Developer Portal microfrontend.\n *\n * This is the main entry point that host applications import and render.\n * It sets up the necessary providers and renders the internal routing.\n * Automatically registers navigation items with the shell if registration functions are provided.\n *\n * @example\n * ```tsx\n * import { DevportalRoot, type DevportalRootProps } from '@burdenoff/microfe-devportal';\n *\n * function App() {\n * const props: DevportalRootProps = {\n * basePath: '/devportal',\n * navigate: (path) => router.push(path),\n * currentUser: { id: '1', email: 'user@example.com' },\n * tenantId: 'tenant-123',\n * apiGatewayUrl: 'https://api.example.com',\n * authToken: 'token',\n * };\n *\n * return <DevportalRoot {...props} />;\n * }\n * ```\n */\nexport const DevportalRoot: FC<DevportalRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = \"/\" } = props;\n const tr = useTr();\n\n // Helper to join paths correctly, avoiding double slashes\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith(\"/\") ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith(\"/\") ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n // Register navigation items with the shell\n useEffect(() => {\n if (!registerNavItems) return;\n\n // Create a parent \"Developer Portal\" item with children for dropdown behavior\n const devportalNavItem: NavItem = {\n id: \"devportal\",\n label: tr(\"devportal.developerPortal\", \"Developer Portal\"),\n path: basePath,\n icon: <Code2 size={20} />,\n order: 75,\n group: \"global\",\n children: [\n {\n id: \"devportal-dashboard\",\n label: tr(\"devportal.dashboard\", \"Dashboard\"),\n path: basePath,\n icon: <LayoutDashboard size={16} />,\n order: 10,\n },\n {\n id: \"devportal-organizations\",\n label: tr(\"devportal.organizations\", \"Organizations\"),\n path: joinPath(basePath, \"organizations\"),\n icon: <Building2 size={16} />,\n order: 20,\n },\n {\n id: \"devportal-apps\",\n label: tr(\"devportal.myApps\", \"My Apps\"),\n path: joinPath(basePath, \"apps\"),\n icon: <AppWindow size={16} />,\n order: 30,\n },\n {\n // TimeCampus applets (BOFF-11005): the third-party applet developer\n // flow for the active workspace.\n id: \"devportal-timecampus-applets\",\n label: tr(\"devportal.nav.timecampusApplets\", \"TimeCampus Applets\"),\n path: joinPath(basePath, \"timecampus-applets\"),\n icon: <Blocks size={16} />,\n order: 35,\n },\n {\n id: \"devportal-oauth\",\n label: tr(\"devportal.oauthClients\", \"OAuth Clients\"),\n path: joinPath(basePath, \"oauth\"),\n icon: <Shield size={16} />,\n order: 40,\n },\n {\n id: \"devportal-api-keys\",\n label: tr(\"devportal.apiKeys\", \"API Keys\"),\n path: joinPath(basePath, \"api-keys\"),\n icon: <Key size={16} />,\n order: 50,\n },\n {\n id: \"devportal-webhooks\",\n label: tr(\"devportal.webhooks\", \"Webhooks\"),\n path: joinPath(basePath, \"webhooks\"),\n icon: <Webhook size={16} />,\n order: 60,\n },\n {\n id: \"devportal-analytics\",\n label: tr(\"devportal.analytics\", \"Analytics\"),\n path: joinPath(basePath, \"analytics\"),\n icon: <BarChart3 size={16} />,\n order: 70,\n },\n {\n id: \"devportal-revenue\",\n label: tr(\"devportal.revenue\", \"Revenue\"),\n path: \"/billing/earnings\",\n icon: <DollarSign size={16} />,\n order: 80,\n },\n {\n id: \"devportal-docs\",\n label: tr(\"devportal.documentation\", \"Documentation\"),\n path: joinPath(basePath, \"docs\"),\n icon: <BookOpen size={16} />,\n order: 90,\n },\n {\n id: \"devportal-resources\",\n label: tr(\"devportal.resources\", \"Resources\"),\n path: joinPath(basePath, \"resources\"),\n icon: <Wrench size={16} />,\n order: 100,\n },\n {\n id: \"devportal-settings\",\n label: tr(\"devportal.settings\", \"Settings\"),\n path: joinPath(basePath, \"settings\"),\n icon: <Settings size={16} />,\n order: 110,\n },\n ],\n };\n\n // Register and get cleanup function\n const cleanup = registerNavItems(devportalNavItem);\n\n // Return cleanup function to unregister on unmount\n return cleanup;\n }, [registerNavItems, basePath, tr]);\n\n // Register footer items\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: \"devportal-status\",\n label: tr(\"devportal.developerPortalReady\", \"Developer Portal Ready\"),\n type: \"status\" as const,\n order: 10,\n section: \"left\" as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems, tr]);\n\n // Register static breadcrumb labels for URL segments that need pretty names.\n // The AutoBreadcrumbs component reads window.__breadcrumbRegistry for all\n // segments (not just UUIDs), so segments like 'oauth' and 'api-keys' get\n // human-readable labels instead of the auto-cased URL segment.\n useEffect(() => {\n const reg = (\n window as unknown as {\n __breadcrumbRegistry?: {\n register: (id: string, name: string) => void;\n unregister: (id: string) => void;\n };\n }\n ).__breadcrumbRegistry;\n if (!reg) return;\n reg.register(\"oauth\", tr(\"devportal.nav.oauth\", \"OAuth Clients\"));\n reg.register(\"api-keys\", tr(\"devportal.nav.apiKeys\", \"API Keys\"));\n return () => {\n reg.unregister(\"oauth\");\n reg.unregister(\"api-keys\");\n };\n }, [tr]);\n\n // Bridge fe-libs event bus -> devportal event bus so MFE pages can emit\n // domain events (`devportal.app.created`, etc.) and the shell sinks\n // (RybbitSink / BackendAuditSink / OtelBrowserSink) consume them centrally.\n // Per memory rule + spec 47: MFEs must not call Rybbit/OTel/fetch directly.\n const sharedBus = useEventBus();\n useEffect(() => {\n setDevportalEventBusBridge(\n sharedBus as unknown as { emit: (n: string, p: unknown) => void },\n );\n return () => setDevportalEventBusBridge(null);\n }, [sharedBus]);\n\n // NOTE: When running in the shell (workspaces-app), the shell's MultiGatewayProvider\n // already provides an ApolloProvider via GatewayRoute. The devportal uses that context.\n // When running standalone, the main.tsx wraps with DevPortalProvider (ApolloProvider).\n return (\n <ErrorBoundary\n fallback={\n <div className=\"p-6 text-center text-text-secondary\">\n {tr(\n \"devportal.error.moduleLoadFailed\",\n \"Something went wrong loading this module. Please refresh the page.\",\n )}\n </div>\n }\n >\n <FeatureFlagProvider\n workspaceId={null}\n gateway=\"global\"\n defaultEnabled={true}\n >\n <DevportalProvider {...props}>\n <DevportalRoutes />\n </DevportalProvider>\n </FeatureFlagProvider>\n </ErrorBoundary>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DevportalRoot.js","names":["MENU_ICONS","LayoutDashboard","Building2","AppWindow","Blocks","Shield","Key","Webhook","BarChart3","DollarSign","BookOpen","Wrench","Settings","UserCircle","MenuIcon","Icon","id","Code2","DevportalRoot","props","tr","useTr","isFluidgrids","joinPath","base","path","useEffect","registerNavItems","devportalNavItem","basePath","getMenuEntries","entry","registerFooterItems","footerItems","reg","sharedBus","useEventBus","setDevportalEventBusBridge","ErrorBoundary","FeatureFlagProvider","DevportalProvider","DevportalRoutes"],"sources":["../../src/devportal/DevportalRoot.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect } from \"react\";\nimport { ErrorBoundary } from \"@burdenoff/fe-libs/shared/components\";\nimport {\n Code2,\n LayoutDashboard,\n Building2,\n AppWindow,\n Blocks,\n BookOpen,\n Settings,\n Webhook,\n Key,\n Shield,\n BarChart3,\n DollarSign,\n Wrench,\n UserCircle,\n} from \"lucide-react\";\nimport { DevportalProvider } from \"./providers/DevportalProvider\";\nimport { FeatureFlagProvider } from \"@burdenoff/fe-libs/shared/feature-flags\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport { useHeadingDocumentTitle } from \"@burdenoff/fe-libs/ui\";\nimport { useTr } from \"./utils/i18n\";\nimport type { NavItem, DevportalRootProps } from \"./types\";\nimport { DevportalRoutes } from \"./DevportalRoutes\";\nimport { setDevportalEventBusBridge } from \"./events\";\nimport { getMenuEntries } from \"./utils/developerNav\";\n\nconst MENU_ICONS: Record<string, typeof Code2> = {\n \"devportal-dashboard\": LayoutDashboard,\n \"devportal-organizations\": Building2,\n \"devportal-apps\": AppWindow,\n \"devportal-timecampus-applets\": Blocks,\n \"devportal-oauth\": Shield,\n \"devportal-api-keys\": Key,\n \"devportal-webhooks\": Webhook,\n \"devportal-analytics\": BarChart3,\n \"devportal-revenue\": DollarSign,\n \"devportal-docs\": BookOpen,\n \"devportal-resources\": Wrench,\n \"devportal-settings\": Settings,\n \"devportal-publisher\": UserCircle,\n};\n\nconst MenuIcon: FC<{ id: string }> = ({ id }) => {\n const Icon = MENU_ICONS[id] ?? Code2;\n return <Icon size={16} />;\n};\n\n/**\n * Root component for the Developer Portal microfrontend.\n *\n * This is the main entry point that host applications import and render.\n * It sets up the necessary providers and renders the internal routing.\n * Automatically registers navigation items with the shell if registration functions are provided.\n *\n * @example\n * ```tsx\n * import { DevportalRoot, type DevportalRootProps } from '@burdenoff/microfe-devportal';\n *\n * function App() {\n * const props: DevportalRootProps = {\n * basePath: '/devportal',\n * navigate: (path) => router.push(path),\n * currentUser: { id: '1', email: 'user@example.com' },\n * tenantId: 'tenant-123',\n * apiGatewayUrl: 'https://api.example.com',\n * authToken: 'token',\n * };\n *\n * return <DevportalRoot {...props} />;\n * }\n * ```\n */\nexport const DevportalRoot: FC<DevportalRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = \"/\" } = props;\n const tr = useTr();\n const isFluidgrids = props.variant === \"fluidgrids\";\n\n // FluidGrids only (BOFF-11862, BOFF-11863): each page's browser tab title follows its main heading.\n useHeadingDocumentTitle({\n enabled: props.variant === \"fluidgrids\",\n suffix: \"FluidGrids\",\n });\n\n // Helper to join paths correctly, avoiding double slashes\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith(\"/\") ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith(\"/\") ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n // Register navigation items with the shell\n useEffect(() => {\n if (!registerNavItems) return;\n\n // Create a parent \"Developer Portal\" item with children for dropdown behavior\n const devportalNavItem: NavItem = {\n id: \"devportal\",\n label: tr(\"devportal.developerPortal\", \"Developer Portal\"),\n path: basePath,\n icon: <Code2 size={20} />,\n order: 75,\n group: \"global\",\n children: getMenuEntries(isFluidgrids).map((entry) => ({\n id: entry.id,\n label: tr(entry.i18nKey, entry.fallback),\n path: entry.absolutePath\n ? entry.absolutePath\n : entry.path\n ? joinPath(basePath, entry.path)\n : basePath,\n icon: <MenuIcon id={entry.id} />,\n order: entry.order,\n })),\n };\n\n // Register and get cleanup function\n const cleanup = registerNavItems(devportalNavItem);\n\n // Return cleanup function to unregister on unmount\n return cleanup;\n }, [registerNavItems, basePath, tr, isFluidgrids]);\n\n // Register footer items\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: \"devportal-status\",\n label: tr(\"devportal.developerPortalReady\", \"Developer Portal Ready\"),\n type: \"status\" as const,\n order: 10,\n section: \"left\" as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems, tr]);\n\n // Register static breadcrumb labels for URL segments that need pretty names.\n // The AutoBreadcrumbs component reads window.__breadcrumbRegistry for all\n // segments (not just UUIDs), so segments like 'oauth' and 'api-keys' get\n // human-readable labels instead of the auto-cased URL segment.\n useEffect(() => {\n const reg = (\n window as unknown as {\n __breadcrumbRegistry?: {\n register: (id: string, name: string) => void;\n unregister: (id: string) => void;\n };\n }\n ).__breadcrumbRegistry;\n if (!reg) return;\n reg.register(\n \"oauth\",\n isFluidgrids\n ? tr(\"devportal.nav.oauthApps\", \"OAuth apps\")\n : tr(\"devportal.nav.oauth\", \"OAuth Clients\"),\n );\n reg.register(\"api-keys\", tr(\"devportal.nav.apiKeys\", \"API Keys\"));\n return () => {\n reg.unregister(\"oauth\");\n reg.unregister(\"api-keys\");\n };\n }, [tr, isFluidgrids]);\n\n // Bridge fe-libs event bus -> devportal event bus so MFE pages can emit\n // domain events (`devportal.app.created`, etc.) and the shell sinks\n // (RybbitSink / BackendAuditSink / OtelBrowserSink) consume them centrally.\n // Per memory rule + spec 47: MFEs must not call Rybbit/OTel/fetch directly.\n const sharedBus = useEventBus();\n useEffect(() => {\n setDevportalEventBusBridge(\n sharedBus as unknown as { emit: (n: string, p: unknown) => void },\n );\n return () => setDevportalEventBusBridge(null);\n }, [sharedBus]);\n\n // NOTE: When running in the shell (workspaces-app), the shell's MultiGatewayProvider\n // already provides an ApolloProvider via GatewayRoute. The devportal uses that context.\n // When running standalone, the main.tsx wraps with DevPortalProvider (ApolloProvider).\n return (\n <ErrorBoundary\n fallback={\n <div className=\"p-6 text-center text-text-secondary\">\n {tr(\n \"devportal.error.moduleLoadFailed\",\n \"Something went wrong loading this module. Please refresh the page.\",\n )}\n </div>\n }\n >\n <FeatureFlagProvider\n workspaceId={null}\n gateway=\"global\"\n defaultEnabled={true}\n >\n <DevportalProvider {...props}>\n <DevportalRoutes />\n </DevportalProvider>\n </FeatureFlagProvider>\n </ErrorBoundary>\n );\n};\n"],"mappings":";;;;;;;;;;;;;AA6BA,IAAMA,IAA2C;CAC/C,uBAAuBC;CACvB,2BAA2BC;CAC3B,kBAAkBC;CAClB,gCAAgCC;CAChC,mBAAmBC;CACnB,sBAAsBC;CACtB,sBAAsBC;CACtB,uBAAuBC;CACvB,qBAAqBC;CACrB,kBAAkBC;CAClB,uBAAuBC;CACvB,sBAAsBC;CACtB,uBAAuBC;AACzB,GAEMC,KAAgC,EAAE,YAAS;CAC/C,IAAMC,IAAOf,EAAWgB,MAAOC;CAC/B,OAAO,kBAACF,GAAD,EAAM,MAAM,GAAK,CAAA;AAC1B,GA2BaG,KAAyC,MAAU;CAC9D,IAAM,EAAE,qBAAkB,wBAAqB,cAAW,QAAQC,GAC5DC,IAAKC,EAAM,GACXC,IAAeH,EAAM,YAAY;CAGvC,EAAwB;EACtB,SAASA,EAAM,YAAY;EAC3B,QAAQ;CACV,CAAC;CAGD,IAAMI,KAAY,GAAc,OACZC,EAAK,SAAS,GAAG,IAAIA,EAAK,MAAM,GAAG,EAAE,IAAIA,MACzCC,EAAK,WAAW,GAAG,IAAIA,IAAO,IAAIA;CA0DtD,AArDAC,QAAgB;EACd,IAAI,CAACC,GAAkB;EAGvB,IAAMC,IAA4B;GAChC,IAAI;GACJ,OAAOR,EAAG,6BAA6B,kBAAkB;GACzD,MAAMS;GACN,MAAM,kBAACZ,GAAD,EAAO,MAAM,GAAK,CAAA;GACxB,OAAO;GACP,OAAO;GACP,UAAUa,EAAeR,CAAY,CAAC,CAAC,KAAK,OAAW;IACrD,IAAIS,EAAM;IACV,OAAOX,EAAGW,EAAM,SAASA,EAAM,QAAQ;IACvC,MAAMA,EAAM,eACRA,EAAM,eACNA,EAAM,OACJR,EAASM,GAAUE,EAAM,IAAI,IAC7BF;IACN,MAAM,kBAACf,GAAD,EAAU,IAAIiB,EAAM,GAAK,CAAA;IAC/B,OAAOA,EAAM;GACf,EAAE;EACJ;EAMA,OAHgBJ,EAAiBC,CAG1B;CACT,GAAG;EAACD;EAAkBE;EAAUT;EAAIE;CAAY,CAAC,GAGjDI,QAAgB;EACd,IAAI,CAACM,GAAqB;EAE1B,IAAMC,IAAc,CAClB;GACE,IAAI;GACJ,OAAOb,EAAG,kCAAkC,wBAAwB;GACpE,MAAM;GACN,OAAO;GACP,SAAS;EACX,CACF;EAGA,OADgBY,EAAoBC,CAC7B;CACT,GAAG,CAACD,GAAqBZ,CAAE,CAAC,GAM5BM,QAAgB;EACd,IAAMQ,IACJ,OAMA;EACF,IAAKA,GAQL,OAPAA,EAAI,SACF,SACAZ,IACIF,EAAG,2BAA2B,YAAY,IAC1CA,EAAG,uBAAuB,eAAe,CAC/C,GACAc,EAAI,SAAS,YAAYd,EAAG,yBAAyB,UAAU,CAAC,SACnD;GAEX,AADAc,EAAI,WAAW,OAAO,GACtBA,EAAI,WAAW,UAAU;EAC3B;CACF,GAAG,CAACd,GAAIE,CAAY,CAAC;CAMrB,IAAMa,IAAYC,EAAY;CAW9B,OAVAV,SACEW,EACEF,CACF,SACaE,EAA2B,IAAI,IAC3C,CAACF,CAAS,CAAC,GAMZ,kBAACG,GAAD;EACE,UACE,kBAAC,OAAD;GAAK,WAAU;GACZ,UAAAlB,EACC,oCACA,oEACF;EACG,CAAA;EAGP,UAAA,kBAACmB,GAAD;GACE,aAAa;GACb,SAAQ;GACR,gBAAgB;GAEhB,UAAA,kBAACC,GAAD;IAAmB,GAAIrB;IACrB,UAAA,kBAACsB,GAAD,CAAkB,CAAA;GACD,CAAA;EACA,CAAA;CACR,CAAA;AAEnB"}
|
|
@@ -6,17 +6,17 @@ import { SelectionCheckbox as o } from "./SelectionCheckbox.js";
|
|
|
6
6
|
import { StoreImage as s } from "./StoreImage.js";
|
|
7
7
|
import { AppWindow as c, Download as l, ExternalLink as u, Star as d } from "lucide-react";
|
|
8
8
|
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { GlassCard as m } from "@burdenoff/fe-libs/ui";
|
|
10
|
+
import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
11
11
|
// src/devportal/components/AppCard.tsx
|
|
12
12
|
var g = ({ app: g, onClick: _, className: v, showActions: y = !0, selected: b = !1, onToggleSelect: x }) => {
|
|
13
|
-
let { navigate: S, basePath: C } = a(), { t: w } =
|
|
13
|
+
let { navigate: S, basePath: C } = a(), { t: w } = h(), T = (e, t) => {
|
|
14
14
|
let n = w(e);
|
|
15
15
|
return n === e ? t : n;
|
|
16
16
|
}, E = () => {
|
|
17
17
|
_ ? _() : S && S(`${C}/apps/${g.id}`);
|
|
18
18
|
};
|
|
19
|
-
return /* @__PURE__ */ p(
|
|
19
|
+
return /* @__PURE__ */ p(m, {
|
|
20
20
|
treatment: "insight",
|
|
21
21
|
role: "button",
|
|
22
22
|
tabIndex: 0,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useDevportal as e } from "../providers/DevportalProvider.js";
|
|
2
|
+
import { useTr as t } from "../utils/i18n.js";
|
|
3
|
+
import { DEVELOPER_SETTINGS_ENTRIES as n, resolveEntryPath as r } from "../utils/developerNav.js";
|
|
4
|
+
import { BarChart3 as i, BookOpen as a, ChevronRight as o, FileClock as s, KeyRound as c, Share2 as l, Shield as u, Webhook as d } from "lucide-react";
|
|
5
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
6
|
+
// src/devportal/components/DeveloperSettingsHub.tsx
|
|
7
|
+
var m = {
|
|
8
|
+
"api-keys": c,
|
|
9
|
+
oauth: u,
|
|
10
|
+
webhooks: d,
|
|
11
|
+
delegations: l,
|
|
12
|
+
"app-logs": s,
|
|
13
|
+
analytics: i,
|
|
14
|
+
docs: a
|
|
15
|
+
}, h = () => {
|
|
16
|
+
let { navigate: i, basePath: a } = e(), s = t();
|
|
17
|
+
return /* @__PURE__ */ f("nav", {
|
|
18
|
+
"aria-label": s("devportal.developerSettings.title", "Developer settings"),
|
|
19
|
+
"data-testid": "developer-settings-hub",
|
|
20
|
+
children: /* @__PURE__ */ f("ul", {
|
|
21
|
+
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
22
|
+
children: n.map((e) => {
|
|
23
|
+
let t = m[e.id] ?? c;
|
|
24
|
+
return /* @__PURE__ */ f("li", { children: /* @__PURE__ */ p("button", {
|
|
25
|
+
type: "button",
|
|
26
|
+
onClick: () => i?.(r(a, e)),
|
|
27
|
+
className: "flex h-full w-full items-start gap-3 rounded-card border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary",
|
|
28
|
+
children: [
|
|
29
|
+
/* @__PURE__ */ f("span", {
|
|
30
|
+
className: "rounded-lg bg-action-primary-bg/10 p-2.5 text-primary",
|
|
31
|
+
children: /* @__PURE__ */ f(t, {
|
|
32
|
+
size: 20,
|
|
33
|
+
"aria-hidden": "true"
|
|
34
|
+
})
|
|
35
|
+
}),
|
|
36
|
+
/* @__PURE__ */ p("span", {
|
|
37
|
+
className: "flex-1",
|
|
38
|
+
children: [/* @__PURE__ */ f("span", {
|
|
39
|
+
className: "block font-medium",
|
|
40
|
+
children: s(e.titleKey, e.titleFallback)
|
|
41
|
+
}), /* @__PURE__ */ f("span", {
|
|
42
|
+
className: "mt-1 block text-sm text-text-secondary",
|
|
43
|
+
children: s(e.descriptionKey, e.descriptionFallback)
|
|
44
|
+
})]
|
|
45
|
+
}),
|
|
46
|
+
/* @__PURE__ */ f(o, {
|
|
47
|
+
size: 16,
|
|
48
|
+
"aria-hidden": "true",
|
|
49
|
+
className: "mt-1 text-text-secondary"
|
|
50
|
+
})
|
|
51
|
+
]
|
|
52
|
+
}) }, e.id);
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
export { h as DeveloperSettingsHub };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=DeveloperSettingsHub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeveloperSettingsHub.js","names":["ICONS","KeyRound","Shield","Webhook","Share2","FileClock","BarChart3","BookOpen","DeveloperSettingsHub","useDevportal","tr","useTr","DEVELOPER_SETTINGS_ENTRIES","Icon","entry","navigate","resolveEntryPath","basePath","ChevronRight"],"sources":["../../../src/devportal/components/DeveloperSettingsHub.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport {\n BarChart3,\n BookOpen,\n ChevronRight,\n FileClock,\n KeyRound,\n Share2,\n Shield,\n Webhook,\n} from \"lucide-react\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { useTr } from \"../utils/i18n\";\nimport {\n DEVELOPER_SETTINGS_ENTRIES,\n resolveEntryPath,\n} from \"../utils/developerNav\";\n\nconst ICONS: Record<string, typeof KeyRound> = {\n \"api-keys\": KeyRound,\n oauth: Shield,\n webhooks: Webhook,\n delegations: Share2,\n \"app-logs\": FileClock,\n analytics: BarChart3,\n docs: BookOpen,\n};\n\n/**\n * FluidGrids only: the single home for the rarer developer pages. Each card\n * opens the page it names; the old URLs keep working.\n */\nexport const DeveloperSettingsHub: FC = () => {\n const { navigate, basePath } = useDevportal();\n const tr = useTr();\n\n return (\n <nav\n aria-label={tr(\"devportal.developerSettings.title\", \"Developer settings\")}\n data-testid=\"developer-settings-hub\"\n >\n <ul className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {DEVELOPER_SETTINGS_ENTRIES.map((entry) => {\n const Icon = ICONS[entry.id] ?? KeyRound;\n return (\n <li key={entry.id}>\n <button\n type=\"button\"\n onClick={() => navigate?.(resolveEntryPath(basePath, entry))}\n className=\"flex h-full w-full items-start gap-3 rounded-card border border-border-seam bg-bg-surface p-4 text-left transition-colors hover:bg-accent focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <span className=\"rounded-lg bg-action-primary-bg/10 p-2.5 text-primary\">\n <Icon size={20} aria-hidden=\"true\" />\n </span>\n <span className=\"flex-1\">\n <span className=\"block font-medium\">\n {tr(entry.titleKey, entry.titleFallback)}\n </span>\n <span className=\"mt-1 block text-sm text-text-secondary\">\n {tr(entry.descriptionKey, entry.descriptionFallback)}\n </span>\n </span>\n <ChevronRight\n size={16}\n aria-hidden=\"true\"\n className=\"mt-1 text-text-secondary\"\n />\n </button>\n </li>\n );\n })}\n </ul>\n </nav>\n );\n};\n"],"mappings":";;;;;;AAkBA,IAAMA,IAAyC;CAC7C,YAAYC;CACZ,OAAOC;CACP,UAAUC;CACV,aAAaC;CACb,YAAYC;CACZ,WAAWC;CACX,MAAMC;AACR,GAMaC,UAAiC;CAC5C,IAAM,EAAE,aAAU,gBAAaC,EAAa,GACtCC,IAAKC,EAAM;CAEjB,OACE,kBAAC,OAAD;EACE,cAAYD,EAAG,qCAAqC,oBAAoB;EACxE,eAAY;EAEZ,UAAA,kBAAC,MAAD;GAAI,WAAU;GACX,UAAAE,EAA2B,KAAK,MAAU;IACzC,IAAMC,IAAOb,EAAMc,EAAM,OAAOb;IAChC,OACE,kBAAC,MAAD,EAAA,UACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAec,IAAWC,EAAiBC,GAAUH,CAAK,CAAC;KAC3D,WAAU;KAHZ,UAAA;MAKE,kBAAC,QAAD;OAAM,WAAU;OACd,UAAA,kBAACD,GAAD;QAAM,MAAM;QAAI,eAAY;OAAQ,CAAA;MAChC,CAAA;MACN,kBAAC,QAAD;OAAM,WAAU;OAAhB,UAAA,CACE,kBAAC,QAAD;QAAM,WAAU;QACb,UAAAH,EAAGI,EAAM,UAAUA,EAAM,aAAa;OACnC,CAAA,GACN,kBAAC,QAAD;QAAM,WAAU;QACb,UAAAJ,EAAGI,EAAM,gBAAgBA,EAAM,mBAAmB;OAC/C,CAAA,CACF;;MACN,kBAACI,GAAD;OACE,MAAM;OACN,eAAY;OACZ,WAAU;MACX,CAAA;KACK;IACN,CAAA,EAAA,GAvBKJ,EAAM,EAuBX;GAER,CAAC;EACC,CAAA;CACD,CAAA;AAET"}
|
|
@@ -6,7 +6,7 @@ interface SectionDef {
|
|
|
6
6
|
fallback: string;
|
|
7
7
|
icon: typeof LayoutDashboard;
|
|
8
8
|
/** Additional sub-paths that should mark this section as active. */
|
|
9
|
-
matchPrefixes?: string[];
|
|
9
|
+
matchPrefixes?: readonly string[];
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Org context switcher — can be embedded standalone or inside SectionNav.
|
|
@@ -4,84 +4,108 @@ import "../hooks/useGraphQL.js";
|
|
|
4
4
|
import { cn as r } from "../utils/cn.js";
|
|
5
5
|
import { useGetMyOrganizationsQuery as i } from "../../generated/global-operations.js";
|
|
6
6
|
import { useTr as a } from "../utils/i18n.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { getSectionEntries as o, getSectionMatchPrefixes as s } from "../utils/developerNav.js";
|
|
8
|
+
import { useEffect as c, useRef as l, useState as u } from "react";
|
|
9
|
+
import { AppWindow as d, BarChart3 as f, Blocks as p, BookOpen as m, Building2 as h, Check as g, ChevronDown as _, DollarSign as v, LayoutDashboard as y, Library as b, Plus as x, Settings as S, Share2 as C, User as w } from "lucide-react";
|
|
9
10
|
import "react-router";
|
|
10
|
-
import { jsx as
|
|
11
|
+
import { jsx as T, jsxs as E } from "react/jsx-runtime";
|
|
11
12
|
// src/devportal/components/SectionNav.tsx
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
var D = {
|
|
14
|
+
dashboard: y,
|
|
15
|
+
apps: d,
|
|
16
|
+
"timecampus-applets": p,
|
|
17
|
+
organizations: h,
|
|
18
|
+
delegations: C,
|
|
19
|
+
revenue: v,
|
|
20
|
+
docs: m,
|
|
21
|
+
resources: b,
|
|
22
|
+
publisher: w,
|
|
23
|
+
settings: S,
|
|
24
|
+
analytics: f
|
|
25
|
+
};
|
|
26
|
+
function O(e) {
|
|
27
|
+
return o(e).map((t) => ({
|
|
28
|
+
path: t.id === "publisher" ? "publisher/profile" : t.path,
|
|
29
|
+
i18nKey: t.i18nKey,
|
|
30
|
+
fallback: t.fallback,
|
|
31
|
+
icon: D[t.id] ?? y,
|
|
32
|
+
matchPrefixes: s(e, t.id)
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
O(!1), O(!0);
|
|
36
|
+
var k = () => {
|
|
37
|
+
let { activeOrgId: o, setActiveOrgId: s, activePhysicalOrgId: d, setActivePhysicalOrgId: f, navigate: p, basePath: m } = e(), v = n(), y = v ? d : o, b = v ? f : s, S = a(), [C, w] = u(!1), D = l(null), { data: O } = i({ fetchPolicy: "cache-first" }), k = (O?.myDeveloperOrganizations ?? []).filter((e) => v ? e.kind === "PHYSICAL" : e.kind !== "PHYSICAL");
|
|
38
|
+
c(() => {
|
|
39
|
+
!y && k.length === 1 && k[0]?.id && b(k[0].id);
|
|
16
40
|
}, [
|
|
17
41
|
k,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
]),
|
|
42
|
+
y,
|
|
43
|
+
b
|
|
44
|
+
]), c(() => {
|
|
21
45
|
let e = (e) => {
|
|
22
|
-
D.current && !D.current.contains(e.target) &&
|
|
46
|
+
D.current && !D.current.contains(e.target) && w(!1);
|
|
23
47
|
};
|
|
24
|
-
return
|
|
25
|
-
}, [
|
|
26
|
-
let A = k.find((e) => e.id ===
|
|
27
|
-
return /* @__PURE__ */
|
|
48
|
+
return C && document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
|
|
49
|
+
}, [C]);
|
|
50
|
+
let A = k.find((e) => e.id === y)?.name ?? (k.length === 0 ? S("devportal.nav.noOrg", "No organization") : S("devportal.nav.selectOrg", "Select org")), j = t(v, "new");
|
|
51
|
+
return /* @__PURE__ */ E("div", {
|
|
28
52
|
ref: D,
|
|
29
53
|
className: "relative flex-shrink-0",
|
|
30
|
-
children: [/* @__PURE__ */
|
|
54
|
+
children: [/* @__PURE__ */ E("button", {
|
|
31
55
|
type: "button",
|
|
32
|
-
onClick: () =>
|
|
56
|
+
onClick: () => w((e) => !e),
|
|
33
57
|
className: r("inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors", "bg-bg-surface text-text-primary border-border-subtle hover:bg-accent"),
|
|
34
|
-
"aria-label":
|
|
58
|
+
"aria-label": S("devportal.nav.switchOrg", "Switch organization"),
|
|
35
59
|
children: [
|
|
36
|
-
/* @__PURE__ */
|
|
60
|
+
/* @__PURE__ */ T(h, {
|
|
37
61
|
size: 13,
|
|
38
62
|
className: "text-primary flex-shrink-0"
|
|
39
63
|
}),
|
|
40
|
-
/* @__PURE__ */
|
|
64
|
+
/* @__PURE__ */ T("span", {
|
|
41
65
|
className: "max-w-[140px] truncate",
|
|
42
66
|
children: A
|
|
43
67
|
}),
|
|
44
|
-
/* @__PURE__ */
|
|
68
|
+
/* @__PURE__ */ T(_, {
|
|
45
69
|
size: 13,
|
|
46
|
-
className: r("transition-transform",
|
|
70
|
+
className: r("transition-transform", C && "rotate-180")
|
|
47
71
|
})
|
|
48
72
|
]
|
|
49
|
-
}),
|
|
73
|
+
}), C && /* @__PURE__ */ E("div", {
|
|
50
74
|
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",
|
|
51
75
|
children: [
|
|
52
|
-
k.map((e) => /* @__PURE__ */
|
|
76
|
+
k.map((e) => /* @__PURE__ */ E("button", {
|
|
53
77
|
type: "button",
|
|
54
78
|
onClick: () => {
|
|
55
|
-
|
|
79
|
+
b(e.id), w(!1);
|
|
56
80
|
},
|
|
57
|
-
className: r("flex w-full items-center gap-2 px-3 py-2 text-sm text-left transition-colors hover:bg-accent", e.id ===
|
|
58
|
-
children: [/* @__PURE__ */
|
|
81
|
+
className: r("flex w-full items-center gap-2 px-3 py-2 text-sm text-left transition-colors hover:bg-accent", e.id === y ? "text-primary font-medium" : "text-text-primary"),
|
|
82
|
+
children: [/* @__PURE__ */ T(g, {
|
|
59
83
|
size: 13,
|
|
60
|
-
className: r("flex-shrink-0", e.id ===
|
|
61
|
-
}), /* @__PURE__ */
|
|
84
|
+
className: r("flex-shrink-0", e.id === y ? "opacity-100 text-primary" : "opacity-0")
|
|
85
|
+
}), /* @__PURE__ */ T("span", {
|
|
62
86
|
className: "truncate",
|
|
63
87
|
children: e.name
|
|
64
88
|
})]
|
|
65
89
|
}, e.id)),
|
|
66
|
-
k.length > 0 && /* @__PURE__ */
|
|
67
|
-
/* @__PURE__ */
|
|
90
|
+
k.length > 0 && /* @__PURE__ */ T("div", { className: "my-1 border-t border-border-subtle" }),
|
|
91
|
+
/* @__PURE__ */ E("button", {
|
|
68
92
|
type: "button",
|
|
69
93
|
onClick: () => {
|
|
70
|
-
|
|
94
|
+
w(!1), p?.(`${m ?? ""}${j}`);
|
|
71
95
|
},
|
|
72
96
|
className: "flex w-full items-center gap-2 px-3 py-2 text-sm text-left text-primary transition-colors hover:bg-accent",
|
|
73
|
-
children: [/* @__PURE__ */
|
|
97
|
+
children: [/* @__PURE__ */ T(x, {
|
|
74
98
|
size: 13,
|
|
75
99
|
className: "flex-shrink-0"
|
|
76
|
-
}), /* @__PURE__ */
|
|
100
|
+
}), /* @__PURE__ */ T("span", {
|
|
77
101
|
className: "truncate",
|
|
78
|
-
children:
|
|
102
|
+
children: S("devportal.nav.newOrganization", "New organization")
|
|
79
103
|
})]
|
|
80
104
|
})
|
|
81
105
|
]
|
|
82
106
|
})]
|
|
83
107
|
});
|
|
84
108
|
};
|
|
85
|
-
export {
|
|
109
|
+
export { k as OrgSwitcher };
|
|
86
110
|
|
|
87
111
|
//# sourceMappingURL=SectionNav.js.map
|
|
@@ -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 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"}
|
|
1
|
+
{"version":3,"file":"SectionNav.js","names":["SECTION_ICONS","LayoutDashboard","AppWindow","Blocks","Building2","Share2","DollarSign","BookOpen","Library","User","Settings","BarChart3","buildSections","getSectionEntries","isFluidgrids","entry","getSectionMatchPrefixes","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","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 BarChart3,\n} from \"lucide-react\";\nimport {\n useDevportal,\n useIsFluidgridsVariant,\n} 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\";\nimport {\n getSectionEntries,\n getSectionMatchPrefixes,\n} from \"../utils/developerNav\";\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?: readonly string[];\n}\n\nconst SECTION_ICONS: Record<string, typeof LayoutDashboard> = {\n dashboard: LayoutDashboard,\n apps: AppWindow,\n \"timecampus-applets\": Blocks,\n organizations: Building2,\n delegations: Share2,\n revenue: DollarSign,\n docs: BookOpen,\n resources: Library,\n publisher: User,\n settings: Settings,\n analytics: BarChart3,\n};\n\n/** Section list for a variant, built from the shared nav data. */\nfunction buildSections(isFluidgrids: boolean): ReadonlyArray<SectionDef> {\n return getSectionEntries(isFluidgrids).map((entry) => ({\n path: entry.id === \"publisher\" ? \"publisher/profile\" : entry.path,\n i18nKey: entry.i18nKey,\n fallback: entry.fallback,\n icon: SECTION_ICONS[entry.id] ?? LayoutDashboard,\n matchPrefixes: getSectionMatchPrefixes(isFluidgrids, entry.id),\n }));\n}\n\nconst SECTIONS: ReadonlyArray<SectionDef> = buildSections(false);\nconst FLUIDGRIDS_SECTIONS: ReadonlyArray<SectionDef> = buildSections(true);\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 const isFluidgrids = useIsFluidgridsVariant();\n const sections = isFluidgrids ? FLUIDGRIDS_SECTIONS : SECTIONS;\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":"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"}
|