@burdenoff/microfe-devportal 2026.1112.2 → 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.
Files changed (78) hide show
  1. package/dist/devportal/DevportalRoot.js +65 -119
  2. package/dist/devportal/DevportalRoot.js.map +1 -1
  3. package/dist/devportal/components/AppCard.js +72 -69
  4. package/dist/devportal/components/AppCard.js.map +1 -1
  5. package/dist/devportal/components/DeveloperSettingsHub.d.ts +6 -0
  6. package/dist/devportal/components/DeveloperSettingsHub.js +59 -0
  7. package/dist/devportal/components/DeveloperSettingsHub.js.map +1 -0
  8. package/dist/devportal/components/ImageUploadField.js +82 -80
  9. package/dist/devportal/components/ImageUploadField.js.map +1 -1
  10. package/dist/devportal/components/SectionNav.d.ts +1 -1
  11. package/dist/devportal/components/SectionNav.js +60 -36
  12. package/dist/devportal/components/SectionNav.js.map +1 -1
  13. package/dist/devportal/components/StoreImage.d.ts +15 -0
  14. package/dist/devportal/components/StoreImage.js +30 -0
  15. package/dist/devportal/components/StoreImage.js.map +1 -0
  16. package/dist/devportal/components/index.d.ts +1 -0
  17. package/dist/devportal/components/index.js +17 -16
  18. package/dist/devportal/hooks/usePackageUpload.d.ts +46 -4
  19. package/dist/devportal/hooks/usePackageUpload.js +90 -36
  20. package/dist/devportal/hooks/usePackageUpload.js.map +1 -1
  21. package/dist/devportal/pages/AdminReviewPage.js +205 -202
  22. package/dist/devportal/pages/AdminReviewPage.js.map +1 -1
  23. package/dist/devportal/pages/AnalyticsDashboardPage.js +186 -179
  24. package/dist/devportal/pages/AnalyticsDashboardPage.js.map +1 -1
  25. package/dist/devportal/pages/ApiKeysPage.js +113 -111
  26. package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
  27. package/dist/devportal/pages/AppLogsPage.js +80 -84
  28. package/dist/devportal/pages/AppLogsPage.js.map +1 -1
  29. package/dist/devportal/pages/ApplicationDetailPage.js +529 -504
  30. package/dist/devportal/pages/ApplicationDetailPage.js.map +1 -1
  31. package/dist/devportal/pages/ApplicationsPage.js +186 -183
  32. package/dist/devportal/pages/ApplicationsPage.js.map +1 -1
  33. package/dist/devportal/pages/CreateApiKeyPage.js +163 -166
  34. package/dist/devportal/pages/CreateApiKeyPage.js.map +1 -1
  35. package/dist/devportal/pages/CreateOAuthClientPage.js +134 -130
  36. package/dist/devportal/pages/CreateOAuthClientPage.js.map +1 -1
  37. package/dist/devportal/pages/CreatePhysicalItemPage.js +2 -2
  38. package/dist/devportal/pages/CreatePhysicalItemPage.js.map +1 -1
  39. package/dist/devportal/pages/DelegationsPage.js +11 -11
  40. package/dist/devportal/pages/DocumentationPage.js +100 -94
  41. package/dist/devportal/pages/DocumentationPage.js.map +1 -1
  42. package/dist/devportal/pages/EditApplicationPage.js +379 -377
  43. package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
  44. package/dist/devportal/pages/EditPhysicalItemPage.js +312 -307
  45. package/dist/devportal/pages/EditPhysicalItemPage.js.map +1 -1
  46. package/dist/devportal/pages/InvitationDecisionPage.js +83 -79
  47. package/dist/devportal/pages/InvitationDecisionPage.js.map +1 -1
  48. package/dist/devportal/pages/ListingPreviewPage.js +175 -171
  49. package/dist/devportal/pages/ListingPreviewPage.js.map +1 -1
  50. package/dist/devportal/pages/OAuthClientDetailPage.js +76 -76
  51. package/dist/devportal/pages/OAuthClientDetailPage.js.map +1 -1
  52. package/dist/devportal/pages/OAuthClientsPage.js +126 -124
  53. package/dist/devportal/pages/OAuthClientsPage.js.map +1 -1
  54. package/dist/devportal/pages/OrganizationDetailPage.js +19 -19
  55. package/dist/devportal/pages/OrganizationMembersPage.js +223 -204
  56. package/dist/devportal/pages/OrganizationMembersPage.js.map +1 -1
  57. package/dist/devportal/pages/ResourcesPage.d.ts +6 -0
  58. package/dist/devportal/pages/ResourcesPage.js +171 -155
  59. package/dist/devportal/pages/ResourcesPage.js.map +1 -1
  60. package/dist/devportal/pages/SellerDashboardPage.js +117 -115
  61. package/dist/devportal/pages/SellerDashboardPage.js.map +1 -1
  62. package/dist/devportal/pages/SellerItemsPage.js +202 -199
  63. package/dist/devportal/pages/SellerItemsPage.js.map +1 -1
  64. package/dist/devportal/pages/SettingsPage.js +330 -315
  65. package/dist/devportal/pages/SettingsPage.js.map +1 -1
  66. package/dist/devportal/pages/SetupPage.js +5 -5
  67. package/dist/devportal/pages/StoreSubmissionPage.js +133 -131
  68. package/dist/devportal/pages/StoreSubmissionPage.js.map +1 -1
  69. package/dist/devportal/pages/WebhookDetailPage.js +6 -6
  70. package/dist/devportal/pages/WebhooksPage.js +39 -39
  71. package/dist/devportal/pages/WebhooksPage.js.map +1 -1
  72. package/dist/devportal/utils/appDetailTabs.d.ts +73 -0
  73. package/dist/devportal/utils/appDetailTabs.js +62 -0
  74. package/dist/devportal/utils/appDetailTabs.js.map +1 -0
  75. package/dist/devportal/utils/developerNav.d.ts +39 -0
  76. package/dist/devportal/utils/developerNav.js +304 -0
  77. package/dist/devportal/utils/developerNav.js.map +1 -0
  78. 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 { useEffect as i } from "react";
6
- import { ErrorBoundary as a } from "@burdenoff/fe-libs/shared/components";
7
- import { AppWindow as o, BarChart3 as s, Blocks as c, BookOpen as l, Building2 as u, Code2 as d, DollarSign as f, Key as p, LayoutDashboard as m, Settings as h, Shield as g, Webhook as _, Wrench as v } from "lucide-react";
8
- import { jsx as y } from "react/jsx-runtime";
9
- import { FeatureFlagProvider as b } from "@burdenoff/fe-libs/shared/feature-flags";
10
- import { useEventBus as x } from "@burdenoff/fe-libs/shared/events";
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 S = (S) => {
13
- let { registerNavItems: C, registerFooterItems: w, basePath: T = "/" } = S, E = t(), D = (e, t) => (e.endsWith("/") ? e.slice(0, -1) : e) + (t.startsWith("/") ? t : `/${t}`);
14
- i(() => {
15
- if (!C) return;
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: E("devportal.developerPortal", "Developer Portal"),
19
- path: T,
20
- icon: /* @__PURE__ */ y(d, { size: 20 }),
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
- id: "devportal-dashboard",
26
- label: E("devportal.dashboard", "Dashboard"),
27
- path: T,
28
- icon: /* @__PURE__ */ y(m, { size: 16 }),
29
- order: 10
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 C(e);
55
+ return c(e);
111
56
  }, [
112
- C,
113
- T,
114
- E
115
- ]), i(() => {
116
- if (!w) return;
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: E("devportal.developerPortalReady", "Developer Portal Ready"),
65
+ label: d("devportal.developerPortalReady", "Developer Portal Ready"),
120
66
  type: "status",
121
67
  order: 10,
122
68
  section: "left"
123
69
  }];
124
- return w(e);
125
- }, [w, E]), i(() => {
70
+ return l(e);
71
+ }, [l, d]), a(() => {
126
72
  let e = window.__breadcrumbRegistry;
127
- if (e) return e.register("oauth", E("devportal.nav.oauth", "OAuth Clients")), e.register("api-keys", E("devportal.nav.apiKeys", "API Keys")), () => {
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
- }, [E]);
131
- let O = x();
132
- return i(() => (r(O), () => r(null)), [O]), /* @__PURE__ */ y(a, {
133
- fallback: /* @__PURE__ */ y("div", {
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: E("devportal.error.moduleLoadFailed", "Something went wrong loading this module. Please refresh the page.")
81
+ children: d("devportal.error.moduleLoadFailed", "Something went wrong loading this module. Please refresh the page.")
136
82
  }),
137
- children: /* @__PURE__ */ y(b, {
83
+ children: /* @__PURE__ */ x(S, {
138
84
  workspaceId: null,
139
85
  gateway: "global",
140
86
  defaultEnabled: !0,
141
- children: /* @__PURE__ */ y(e, {
142
- ...S,
143
- children: /* @__PURE__ */ y(n, {})
87
+ children: /* @__PURE__ */ x(e, {
88
+ ...s,
89
+ children: /* @__PURE__ */ x(n, {})
144
90
  })
145
91
  })
146
92
  });
147
93
  };
148
- export { S as DevportalRoot };
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"}
@@ -3,144 +3,147 @@ import { FeaturedBadge as t, StatusBadge as n, VerifiedBadge as r } from "./Stat
3
3
  import { APP_TYPE_LABELS as i } from "../utils/constants.js";
4
4
  import { useDevportal as a } from "../providers/DevportalProvider.js";
5
5
  import { SelectionCheckbox as o } from "./SelectionCheckbox.js";
6
- import { AppWindow as s, Download as c, ExternalLink as l, Star as u } from "lucide-react";
7
- import { jsx as d, jsxs as f } from "react/jsx-runtime";
8
- import { useI18n as p } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
6
+ import { StoreImage as s } from "./StoreImage.js";
7
+ import { AppWindow as c, Download as l, ExternalLink as u, Star as d } from "lucide-react";
8
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
9
9
  import { GlassCard as m } from "@burdenoff/fe-libs/ui";
10
+ import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
10
11
  // src/devportal/components/AppCard.tsx
11
- var h = ({ app: h, onClick: g, className: _, showActions: v = !0, selected: y = !1, onToggleSelect: b }) => {
12
- let { navigate: x, basePath: S } = a(), { t: C } = p(), w = (e, t) => {
13
- let n = C(e);
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 } = h(), T = (e, t) => {
14
+ let n = w(e);
14
15
  return n === e ? t : n;
15
- }, T = () => {
16
- g ? g() : x && x(`${S}/apps/${h.id}`);
16
+ }, E = () => {
17
+ _ ? _() : S && S(`${C}/apps/${g.id}`);
17
18
  };
18
- return /* @__PURE__ */ f(m, {
19
+ return /* @__PURE__ */ p(m, {
19
20
  treatment: "insight",
20
21
  role: "button",
21
22
  tabIndex: 0,
22
- className: e("group cursor-pointer border-border-seam p-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", y && "border-primary bg-action-primary-bg/5", _),
23
- onClick: T,
23
+ className: e("group cursor-pointer border-border-seam p-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", b && "border-primary bg-action-primary-bg/5", v),
24
+ onClick: E,
24
25
  onKeyDown: (e) => {
25
- (e.key === "Enter" || e.key === " ") && (e.preventDefault(), T());
26
+ (e.key === "Enter" || e.key === " ") && (e.preventDefault(), E());
26
27
  },
27
28
  children: [
28
- /* @__PURE__ */ f("div", {
29
+ /* @__PURE__ */ p("div", {
29
30
  className: "flex items-start gap-3",
30
31
  children: [
31
- b && /* @__PURE__ */ d("div", {
32
+ x && /* @__PURE__ */ f("div", {
32
33
  onClick: (e) => e.stopPropagation(),
33
- children: /* @__PURE__ */ d(o, {
34
- checked: y,
35
- onChange: b,
36
- ariaLabel: `Select application ${h.name}`,
34
+ children: /* @__PURE__ */ f(o, {
35
+ checked: b,
36
+ onChange: x,
37
+ ariaLabel: `Select application ${g.name}`,
37
38
  className: "mt-1"
38
39
  })
39
40
  }),
40
- h.iconUrl ? /* @__PURE__ */ d("img", {
41
- src: h.iconUrl,
42
- alt: h.name,
43
- className: "h-12 w-12 rounded-lg object-cover"
44
- }) : /* @__PURE__ */ d("div", {
45
- className: "flex h-12 w-12 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary",
46
- children: /* @__PURE__ */ d(s, { size: 24 })
41
+ /* @__PURE__ */ f(s, {
42
+ src: g.iconUrl,
43
+ alt: g.name,
44
+ className: "h-12 w-12 rounded-lg object-cover",
45
+ fallback: /* @__PURE__ */ f("div", {
46
+ className: "flex h-12 w-12 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary",
47
+ children: /* @__PURE__ */ f(c, { size: 24 })
48
+ })
47
49
  }),
48
- /* @__PURE__ */ f("div", {
50
+ /* @__PURE__ */ p("div", {
49
51
  className: "flex-1 min-w-0",
50
- children: [/* @__PURE__ */ f("div", {
52
+ children: [/* @__PURE__ */ p("div", {
51
53
  className: "flex items-center gap-2",
52
- children: [/* @__PURE__ */ d("h3", {
54
+ children: [/* @__PURE__ */ f("h3", {
53
55
  className: "font-semibold truncate",
54
- children: h.name
55
- }), h.isVerified && /* @__PURE__ */ d(r, { verified: !0 })]
56
- }), /* @__PURE__ */ d("p", {
56
+ children: g.name
57
+ }), g.isVerified && /* @__PURE__ */ f(r, { verified: !0 })]
58
+ }), /* @__PURE__ */ f("p", {
57
59
  className: "text-sm text-text-secondary",
58
- children: i[h.appType] || h.appType
60
+ children: i[g.appType] || g.appType
59
61
  })]
60
62
  })
61
63
  ]
62
64
  }),
63
- h.shortDescription && /* @__PURE__ */ d("p", {
65
+ g.shortDescription && /* @__PURE__ */ f("p", {
64
66
  className: "mt-3 text-sm text-text-secondary line-clamp-2",
65
- children: h.shortDescription
67
+ children: g.shortDescription
66
68
  }),
67
- /* @__PURE__ */ f("div", {
69
+ /* @__PURE__ */ p("div", {
68
70
  className: "mt-3 flex flex-wrap items-center gap-2",
69
- children: [/* @__PURE__ */ d(n, { status: h.status }), h.isFeatured && /* @__PURE__ */ d(t, {})]
71
+ children: [/* @__PURE__ */ f(n, { status: g.status }), g.isFeatured && /* @__PURE__ */ f(t, {})]
70
72
  }),
71
- /* @__PURE__ */ f("div", {
73
+ /* @__PURE__ */ p("div", {
72
74
  className: "mt-4 flex items-center gap-4 text-sm text-text-secondary",
73
- children: [h.averageRating !== void 0 && h.averageRating > 0 && /* @__PURE__ */ f("span", {
75
+ children: [g.averageRating !== void 0 && g.averageRating > 0 && /* @__PURE__ */ p("span", {
74
76
  className: "flex items-center gap-1",
75
- children: [/* @__PURE__ */ d(u, {
77
+ children: [/* @__PURE__ */ f(d, {
76
78
  size: 14,
77
79
  className: "text-status-warning-text fill-status-warning-text"
78
- }), h.averageRating.toFixed(1)]
79
- }), /* @__PURE__ */ f("span", {
80
+ }), g.averageRating.toFixed(1)]
81
+ }), /* @__PURE__ */ p("span", {
80
82
  className: "flex items-center gap-1",
81
83
  children: [
82
- /* @__PURE__ */ d(c, { size: 14 }),
83
- h.installCount.toLocaleString(),
84
+ /* @__PURE__ */ f(l, { size: 14 }),
85
+ g.installCount.toLocaleString(),
84
86
  " ",
85
- w("devportal.installs", "installs")
87
+ T("devportal.installs", "installs")
86
88
  ]
87
89
  })]
88
90
  }),
89
- v && /* @__PURE__ */ d("div", {
91
+ y && /* @__PURE__ */ f("div", {
90
92
  className: "mt-4 flex items-center justify-between opacity-0 group-hover:opacity-100 transition-opacity",
91
- children: /* @__PURE__ */ f("button", {
93
+ children: /* @__PURE__ */ p("button", {
92
94
  className: "text-sm text-primary hover:underline flex items-center gap-1",
93
95
  onClick: (e) => {
94
- e.stopPropagation(), T();
96
+ e.stopPropagation(), E();
95
97
  },
96
- children: [w("devportal.viewDetails", "View Details"), /* @__PURE__ */ d(l, { size: 14 })]
98
+ children: [T("devportal.viewDetails", "View Details"), /* @__PURE__ */ f(u, { size: 14 })]
97
99
  })
98
100
  })
99
101
  ]
100
102
  });
101
- }, g = ({ app: t, onClick: r, className: c, selected: l = !1, onToggleSelect: u }) => {
102
- let { navigate: p, basePath: m } = a(), h = () => {
103
- r ? r() : p && p(`${m}/apps/${t.id}`);
103
+ }, _ = ({ app: t, onClick: r, className: l, selected: u = !1, onToggleSelect: d }) => {
104
+ let { navigate: m, basePath: h } = a(), g = () => {
105
+ r ? r() : m && m(`${h}/apps/${t.id}`);
104
106
  };
105
- return /* @__PURE__ */ f("div", {
107
+ return /* @__PURE__ */ p("div", {
106
108
  role: "button",
107
109
  tabIndex: 0,
108
- className: e("flex items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-3 cursor-pointer transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", l && "border-primary bg-action-primary-bg/5", c),
109
- onClick: h,
110
+ className: e("flex items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-3 cursor-pointer transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", u && "border-primary bg-action-primary-bg/5", l),
111
+ onClick: g,
110
112
  onKeyDown: (e) => {
111
- (e.key === "Enter" || e.key === " ") && (e.preventDefault(), h());
113
+ (e.key === "Enter" || e.key === " ") && (e.preventDefault(), g());
112
114
  },
113
115
  children: [
114
- u && /* @__PURE__ */ d("div", {
116
+ d && /* @__PURE__ */ f("div", {
115
117
  onClick: (e) => e.stopPropagation(),
116
- children: /* @__PURE__ */ d(o, {
117
- checked: l,
118
- onChange: u,
118
+ children: /* @__PURE__ */ f(o, {
119
+ checked: u,
120
+ onChange: d,
119
121
  ariaLabel: `Select application ${t.name}`
120
122
  })
121
123
  }),
122
- t.iconUrl ? /* @__PURE__ */ d("img", {
124
+ /* @__PURE__ */ f(s, {
123
125
  src: t.iconUrl,
124
126
  alt: t.name,
125
- className: "h-10 w-10 rounded-lg object-cover"
126
- }) : /* @__PURE__ */ d("div", {
127
- className: "flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary",
128
- children: /* @__PURE__ */ d(s, { size: 20 })
127
+ className: "h-10 w-10 rounded-lg object-cover",
128
+ fallback: /* @__PURE__ */ f("div", {
129
+ className: "flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary",
130
+ children: /* @__PURE__ */ f(c, { size: 20 })
131
+ })
129
132
  }),
130
- /* @__PURE__ */ f("div", {
133
+ /* @__PURE__ */ p("div", {
131
134
  className: "flex-1 min-w-0",
132
- children: [/* @__PURE__ */ d("p", {
135
+ children: [/* @__PURE__ */ f("p", {
133
136
  className: "font-medium truncate",
134
137
  children: t.name
135
- }), /* @__PURE__ */ d("p", {
138
+ }), /* @__PURE__ */ f("p", {
136
139
  className: "text-sm text-text-secondary truncate",
137
140
  children: t.shortDescription || i[t.appType]
138
141
  })]
139
142
  }),
140
- /* @__PURE__ */ d(n, { status: t.status })
143
+ /* @__PURE__ */ f(n, { status: t.status })
141
144
  ]
142
145
  });
143
146
  };
144
- export { h as AppCard, g as AppListItem };
147
+ export { g as AppCard, _ as AppListItem };
145
148
 
146
149
  //# sourceMappingURL=AppCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AppCard.js","names":["AppCard","useDevportal","useI18n","tr","value","t","key","fallback","handleClick","onClick","navigate","basePath","app","GlassCard","cn","selected","className","event","onToggleSelect","SelectionCheckbox","AppWindow","VerifiedBadge","APP_TYPE_LABELS","StatusBadge","FeaturedBadge","Star","Download","showActions","ExternalLink","AppListItem"],"sources":["../../../src/devportal/components/AppCard.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { AppWindow, Star, Download, ExternalLink } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { cn } from \"../utils/cn\";\nimport { StatusBadge, VerifiedBadge, FeaturedBadge } from \"./StatusBadge\";\nimport type { Application } from \"../types\";\nimport { APP_TYPE_LABELS } from \"../utils/constants\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { SelectionCheckbox } from \"./SelectionCheckbox\";\n\ninterface AppCardProps {\n app: Application;\n onClick?: () => void;\n className?: string;\n showActions?: boolean;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n}\n\n/**\n * Application card for displaying app information\n */\nexport const AppCard: FC<AppCardProps> = ({\n app,\n onClick,\n className,\n showActions = true,\n selected = false,\n onToggleSelect,\n}) => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const handleClick = () => {\n if (onClick) {\n onClick();\n } else if (navigate) {\n navigate(`${basePath}/apps/${app.id}`);\n }\n };\n\n return (\n <GlassCard\n treatment=\"insight\"\n role=\"button\"\n tabIndex={0}\n className={cn(\n \"group cursor-pointer border-border-seam p-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n selected && \"border-primary bg-action-primary-bg/5\",\n className,\n )}\n onClick={handleClick}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n handleClick();\n }\n }}\n >\n {/* Header */}\n <div className=\"flex items-start gap-3\">\n {onToggleSelect && (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selected}\n onChange={onToggleSelect}\n ariaLabel={`Select application ${app.name}`}\n className=\"mt-1\"\n />\n </div>\n )}\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt={app.name}\n className=\"h-12 w-12 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex h-12 w-12 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <AppWindow size={24} />\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold truncate\">{app.name}</h3>\n {app.isVerified && <VerifiedBadge verified />}\n </div>\n <p className=\"text-sm text-text-secondary\">\n {APP_TYPE_LABELS[app.appType] || app.appType}\n </p>\n </div>\n </div>\n\n {/* Description */}\n {app.shortDescription && (\n <p className=\"mt-3 text-sm text-text-secondary line-clamp-2\">\n {app.shortDescription}\n </p>\n )}\n\n {/* Badges */}\n <div className=\"mt-3 flex flex-wrap items-center gap-2\">\n <StatusBadge status={app.status} />\n {app.isFeatured && <FeaturedBadge />}\n </div>\n\n {/* Stats */}\n <div className=\"mt-4 flex items-center gap-4 text-sm text-text-secondary\">\n {app.averageRating !== undefined && app.averageRating > 0 && (\n <span className=\"flex items-center gap-1\">\n <Star\n size={14}\n className=\"text-status-warning-text fill-status-warning-text\"\n />\n {app.averageRating.toFixed(1)}\n </span>\n )}\n <span className=\"flex items-center gap-1\">\n <Download size={14} />\n {app.installCount.toLocaleString()}{\" \"}\n {tr(\"devportal.installs\", \"installs\")}\n </span>\n </div>\n\n {/* Actions */}\n {showActions && (\n <div className=\"mt-4 flex items-center justify-between opacity-0 group-hover:opacity-100 transition-opacity\">\n <button\n className=\"text-sm text-primary hover:underline flex items-center gap-1\"\n onClick={(e) => {\n e.stopPropagation();\n handleClick();\n }}\n >\n {tr(\"devportal.viewDetails\", \"View Details\")}\n <ExternalLink size={14} />\n </button>\n </div>\n )}\n </GlassCard>\n );\n};\n\n/**\n * Grid of app cards\n */\nexport const AppCardGrid: FC<{\n apps: Application[];\n onAppClick?: (app: Application) => void;\n className?: string;\n}> = ({ apps, onAppClick, className }) => (\n <div\n className={cn(\n \"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\",\n className,\n )}\n >\n {apps.map((app) => (\n <AppCard\n key={app.id}\n app={app}\n onClick={onAppClick ? () => onAppClick(app) : undefined}\n />\n ))}\n </div>\n);\n\n/**\n * Compact app list item\n */\nexport const AppListItem: FC<{\n app: Application;\n onClick?: () => void;\n className?: string;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n}> = ({ app, onClick, className, selected = false, onToggleSelect }) => {\n const { navigate, basePath } = useDevportal();\n\n const handleClick = () => {\n if (onClick) {\n onClick();\n } else if (navigate) {\n navigate(`${basePath}/apps/${app.id}`);\n }\n };\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n className={cn(\n \"flex items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-3 cursor-pointer transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n selected && \"border-primary bg-action-primary-bg/5\",\n className,\n )}\n onClick={handleClick}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n handleClick();\n }\n }}\n >\n {onToggleSelect && (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selected}\n onChange={onToggleSelect}\n ariaLabel={`Select application ${app.name}`}\n />\n </div>\n )}\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt={app.name}\n className=\"h-10 w-10 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <AppWindow size={20} />\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium truncate\">{app.name}</p>\n <p className=\"text-sm text-text-secondary truncate\">\n {app.shortDescription || APP_TYPE_LABELS[app.appType]}\n </p>\n </div>\n <StatusBadge status={app.status} />\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AppCard.js","names":["AppCard","useDevportal","useI18n","tr","value","t","key","fallback","handleClick","onClick","navigate","basePath","app","GlassCard","cn","selected","className","event","onToggleSelect","SelectionCheckbox","StoreImage","AppWindow","VerifiedBadge","APP_TYPE_LABELS","StatusBadge","FeaturedBadge","Star","Download","showActions","ExternalLink","AppListItem"],"sources":["../../../src/devportal/components/AppCard.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { AppWindow, Star, Download, ExternalLink } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { cn } from \"../utils/cn\";\nimport { StatusBadge, VerifiedBadge, FeaturedBadge } from \"./StatusBadge\";\nimport type { Application } from \"../types\";\nimport { APP_TYPE_LABELS } from \"../utils/constants\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { SelectionCheckbox } from \"./SelectionCheckbox\";\nimport { StoreImage } from \"./StoreImage\";\n\ninterface AppCardProps {\n app: Application;\n onClick?: () => void;\n className?: string;\n showActions?: boolean;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n}\n\n/**\n * Application card for displaying app information\n */\nexport const AppCard: FC<AppCardProps> = ({\n app,\n onClick,\n className,\n showActions = true,\n selected = false,\n onToggleSelect,\n}) => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const handleClick = () => {\n if (onClick) {\n onClick();\n } else if (navigate) {\n navigate(`${basePath}/apps/${app.id}`);\n }\n };\n\n return (\n <GlassCard\n treatment=\"insight\"\n role=\"button\"\n tabIndex={0}\n className={cn(\n \"group cursor-pointer border-border-seam p-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n selected && \"border-primary bg-action-primary-bg/5\",\n className,\n )}\n onClick={handleClick}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n handleClick();\n }\n }}\n >\n {/* Header */}\n <div className=\"flex items-start gap-3\">\n {onToggleSelect && (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selected}\n onChange={onToggleSelect}\n ariaLabel={`Select application ${app.name}`}\n className=\"mt-1\"\n />\n </div>\n )}\n <StoreImage\n src={app.iconUrl}\n alt={app.name}\n className=\"h-12 w-12 rounded-lg object-cover\"\n fallback={\n <div className=\"flex h-12 w-12 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <AppWindow size={24} />\n </div>\n }\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold truncate\">{app.name}</h3>\n {app.isVerified && <VerifiedBadge verified />}\n </div>\n <p className=\"text-sm text-text-secondary\">\n {APP_TYPE_LABELS[app.appType] || app.appType}\n </p>\n </div>\n </div>\n\n {/* Description */}\n {app.shortDescription && (\n <p className=\"mt-3 text-sm text-text-secondary line-clamp-2\">\n {app.shortDescription}\n </p>\n )}\n\n {/* Badges */}\n <div className=\"mt-3 flex flex-wrap items-center gap-2\">\n <StatusBadge status={app.status} />\n {app.isFeatured && <FeaturedBadge />}\n </div>\n\n {/* Stats */}\n <div className=\"mt-4 flex items-center gap-4 text-sm text-text-secondary\">\n {app.averageRating !== undefined && app.averageRating > 0 && (\n <span className=\"flex items-center gap-1\">\n <Star\n size={14}\n className=\"text-status-warning-text fill-status-warning-text\"\n />\n {app.averageRating.toFixed(1)}\n </span>\n )}\n <span className=\"flex items-center gap-1\">\n <Download size={14} />\n {app.installCount.toLocaleString()}{\" \"}\n {tr(\"devportal.installs\", \"installs\")}\n </span>\n </div>\n\n {/* Actions */}\n {showActions && (\n <div className=\"mt-4 flex items-center justify-between opacity-0 group-hover:opacity-100 transition-opacity\">\n <button\n className=\"text-sm text-primary hover:underline flex items-center gap-1\"\n onClick={(e) => {\n e.stopPropagation();\n handleClick();\n }}\n >\n {tr(\"devportal.viewDetails\", \"View Details\")}\n <ExternalLink size={14} />\n </button>\n </div>\n )}\n </GlassCard>\n );\n};\n\n/**\n * Grid of app cards\n */\nexport const AppCardGrid: FC<{\n apps: Application[];\n onAppClick?: (app: Application) => void;\n className?: string;\n}> = ({ apps, onAppClick, className }) => (\n <div\n className={cn(\n \"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\",\n className,\n )}\n >\n {apps.map((app) => (\n <AppCard\n key={app.id}\n app={app}\n onClick={onAppClick ? () => onAppClick(app) : undefined}\n />\n ))}\n </div>\n);\n\n/**\n * Compact app list item\n */\nexport const AppListItem: FC<{\n app: Application;\n onClick?: () => void;\n className?: string;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n}> = ({ app, onClick, className, selected = false, onToggleSelect }) => {\n const { navigate, basePath } = useDevportal();\n\n const handleClick = () => {\n if (onClick) {\n onClick();\n } else if (navigate) {\n navigate(`${basePath}/apps/${app.id}`);\n }\n };\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n className={cn(\n \"flex items-center gap-3 rounded-lg border border-border-seam bg-bg-surface p-3 cursor-pointer transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n selected && \"border-primary bg-action-primary-bg/5\",\n className,\n )}\n onClick={handleClick}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n handleClick();\n }\n }}\n >\n {onToggleSelect && (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selected}\n onChange={onToggleSelect}\n ariaLabel={`Select application ${app.name}`}\n />\n </div>\n )}\n <StoreImage\n src={app.iconUrl}\n alt={app.name}\n className=\"h-10 w-10 rounded-lg object-cover\"\n fallback={\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <AppWindow size={20} />\n </div>\n }\n />\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium truncate\">{app.name}</p>\n <p className=\"text-sm text-text-secondary truncate\">\n {app.shortDescription || APP_TYPE_LABELS[app.appType]}\n </p>\n </div>\n <StatusBadge status={app.status} />\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;AAwBA,IAAaA,KAA6B,EACxC,QACA,YACA,cACA,iBAAc,IACd,cAAW,IACX,wBACI;CACJ,IAAM,EAAE,aAAU,gBAAaC,EAAa,GACtC,EAAE,SAAMC,EAAQ,GAChBC,KAAM,GAAa,MAA6B;EACpD,IAAMC,IAAQC,EAAEC,CAAG;EACnB,OAAOF,MAAUE,IAAMC,IAAWH;CACpC,GAEMI,UAAoB;EACxB,AAAIC,IACFA,EAAQ,IACCC,KACTA,EAAS,GAAGC,EAAS,QAAQC,EAAI,IAAI;CAEzC;CAEA,OACE,kBAACC,GAAD;EACE,WAAU;EACV,MAAK;EACL,UAAU;EACV,WAAWC,EACT,mJACAC,KAAY,yCACZC,CACF;EACA,SAASR;EACT,YAAY,MAAU;GACpB,CAAIS,EAAM,QAAQ,WAAWA,EAAM,QAAQ,SACzCA,EAAM,eAAe,GACrBT,EAAY;EAEhB;EAfF,UAAA;GAkBE,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACGU,KACC,kBAAC,OAAD;MAAK,UAAU,MAAUD,EAAM,gBAAgB;MAC7C,UAAA,kBAACE,GAAD;OACE,SAASJ;OACT,UAAUG;OACV,WAAW,sBAAsBN,EAAI;OACrC,WAAU;MACX,CAAA;KACE,CAAA;KAEP,kBAACQ,GAAD;MACE,KAAKR,EAAI;MACT,KAAKA,EAAI;MACT,WAAU;MACV,UACE,kBAAC,OAAD;OAAK,WAAU;OACb,UAAA,kBAACS,GAAD,EAAW,MAAM,GAAK,CAAA;MACnB,CAAA;KAER,CAAA;KACD,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;QAA0B,UAAAT,EAAI;OAAS,CAAA,GACpDA,EAAI,cAAc,kBAACU,GAAD,EAAe,UAAA,GAAU,CAAA,CACzC;MACL,CAAA,GAAA,kBAAC,KAAD;OAAG,WAAU;OACV,UAAAC,EAAgBX,EAAI,YAAYA,EAAI;MACpC,CAAA,CACA;;IACF;;GAGJA,EAAI,oBACH,kBAAC,KAAD;IAAG,WAAU;IACV,UAAAA,EAAI;GACJ,CAAA;GAIL,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAACY,GAAD,EAAa,QAAQZ,EAAI,OAAS,CAAA,GACjCA,EAAI,cAAc,kBAACa,GAAD,CAAgB,CAAA,CAChC;;GAGL,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACGb,EAAI,kBAAkB,KAAA,KAAaA,EAAI,gBAAgB,KACtD,kBAAC,QAAD;KAAM,WAAU;KAAhB,UAAA,CACE,kBAACc,GAAD;MACE,MAAM;MACN,WAAU;KACX,CAAA,GACAd,EAAI,cAAc,QAAQ,CAAC,CACxB;IAER,CAAA,GAAA,kBAAC,QAAD;KAAM,WAAU;KAAhB,UAAA;MACE,kBAACe,GAAD,EAAU,MAAM,GAAK,CAAA;MACpBf,EAAI,aAAa,eAAe;MAAG;MACnCT,EAAG,sBAAsB,UAAU;KAChC;IACH,CAAA,CAAA;;GAGJyB,KACC,kBAAC,OAAD;IAAK,WAAU;IACb,UAAA,kBAAC,UAAD;KACE,WAAU;KACV,UAAU,MAAM;MAEd,AADA,EAAE,gBAAgB,GAClBpB,EAAY;KACd;KALF,UAAA,CAOGL,EAAG,yBAAyB,cAAc,GAC3C,kBAAC0B,GAAD,EAAc,MAAM,GAAK,CAAA,CACnB;;GACL,CAAA;EAEE;;AAEf,GA6BaC,KAMP,EAAE,QAAK,YAAS,cAAW,cAAW,IAAO,wBAAqB;CACtE,IAAM,EAAE,aAAU,gBAAa7B,EAAa,GAEtCO,UAAoB;EACxB,AAAIC,IACFA,EAAQ,IACCC,KACTA,EAAS,GAAGC,EAAS,QAAQC,EAAI,IAAI;CAEzC;CAEA,OACE,kBAAC,OAAD;EACE,MAAK;EACL,UAAU;EACV,WAAWE,EACT,uOACAC,KAAY,yCACZC,CACF;EACA,SAASR;EACT,YAAY,MAAU;GACpB,CAAIS,EAAM,QAAQ,WAAWA,EAAM,QAAQ,SACzCA,EAAM,eAAe,GACrBT,EAAY;EAEhB;EAdF,UAAA;GAgBGU,KACC,kBAAC,OAAD;IAAK,UAAU,MAAUD,EAAM,gBAAgB;IAC7C,UAAA,kBAACE,GAAD;KACE,SAASJ;KACT,UAAUG;KACV,WAAW,sBAAsBN,EAAI;IACtC,CAAA;GACE,CAAA;GAEP,kBAACQ,GAAD;IACE,KAAKR,EAAI;IACT,KAAKA,EAAI;IACT,WAAU;IACV,UACE,kBAAC,OAAD;KAAK,WAAU;KACb,UAAA,kBAACS,GAAD,EAAW,MAAM,GAAK,CAAA;IACnB,CAAA;GAER,CAAA;GACD,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;KAAwB,UAAAT,EAAI;IAAQ,CAAA,GACjD,kBAAC,KAAD;KAAG,WAAU;KACV,UAAAA,EAAI,oBAAoBW,EAAgBX,EAAI;IAC5C,CAAA,CACA;;GACL,kBAACY,GAAD,EAAa,QAAQZ,EAAI,OAAS,CAAA;EAC/B;;AAET"}
@@ -0,0 +1,6 @@
1
+ import { FC } from 'react';
2
+ /**
3
+ * FluidGrids only: the single home for the rarer developer pages. Each card
4
+ * opens the page it names; the old URLs keep working.
5
+ */
6
+ export declare const DeveloperSettingsHub: FC;
@@ -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"}