@burdenoff/microfe-devportal 2026.1113.1 → 2026.1113.3

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 (55) 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 +4 -4
  4. package/dist/devportal/components/DeveloperSettingsHub.d.ts +6 -0
  5. package/dist/devportal/components/DeveloperSettingsHub.js +59 -0
  6. package/dist/devportal/components/DeveloperSettingsHub.js.map +1 -0
  7. package/dist/devportal/components/SectionNav.d.ts +1 -1
  8. package/dist/devportal/components/SectionNav.js +60 -36
  9. package/dist/devportal/components/SectionNav.js.map +1 -1
  10. package/dist/devportal/pages/AnalyticsDashboardPage.js +186 -179
  11. package/dist/devportal/pages/AnalyticsDashboardPage.js.map +1 -1
  12. package/dist/devportal/pages/ApiKeysPage.js +113 -111
  13. package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
  14. package/dist/devportal/pages/AppLogsPage.js +80 -84
  15. package/dist/devportal/pages/AppLogsPage.js.map +1 -1
  16. package/dist/devportal/pages/ApplicationDetailPage.js +529 -504
  17. package/dist/devportal/pages/ApplicationDetailPage.js.map +1 -1
  18. package/dist/devportal/pages/ApplicationsPage.js +68 -67
  19. package/dist/devportal/pages/ApplicationsPage.js.map +1 -1
  20. package/dist/devportal/pages/CreateApiKeyPage.js +163 -166
  21. package/dist/devportal/pages/CreateApiKeyPage.js.map +1 -1
  22. package/dist/devportal/pages/CreateOAuthClientPage.js +134 -130
  23. package/dist/devportal/pages/CreateOAuthClientPage.js.map +1 -1
  24. package/dist/devportal/pages/DelegationsPage.js +11 -11
  25. package/dist/devportal/pages/DocumentationPage.js +100 -94
  26. package/dist/devportal/pages/DocumentationPage.js.map +1 -1
  27. package/dist/devportal/pages/EditApplicationPage.js +379 -377
  28. package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
  29. package/dist/devportal/pages/InvitationDecisionPage.js +83 -79
  30. package/dist/devportal/pages/InvitationDecisionPage.js.map +1 -1
  31. package/dist/devportal/pages/OAuthClientDetailPage.js +76 -76
  32. package/dist/devportal/pages/OAuthClientDetailPage.js.map +1 -1
  33. package/dist/devportal/pages/OAuthClientsPage.js +126 -124
  34. package/dist/devportal/pages/OAuthClientsPage.js.map +1 -1
  35. package/dist/devportal/pages/OrganizationDetailPage.js +19 -19
  36. package/dist/devportal/pages/OrganizationMembersPage.js +223 -204
  37. package/dist/devportal/pages/OrganizationMembersPage.js.map +1 -1
  38. package/dist/devportal/pages/ResourcesPage.d.ts +6 -0
  39. package/dist/devportal/pages/ResourcesPage.js +171 -155
  40. package/dist/devportal/pages/ResourcesPage.js.map +1 -1
  41. package/dist/devportal/pages/SellerDashboardPage.js +13 -13
  42. package/dist/devportal/pages/SellerItemsPage.js +4 -4
  43. package/dist/devportal/pages/SettingsPage.js +330 -315
  44. package/dist/devportal/pages/SettingsPage.js.map +1 -1
  45. package/dist/devportal/pages/SetupPage.js +5 -5
  46. package/dist/devportal/pages/WebhookDetailPage.js +6 -6
  47. package/dist/devportal/pages/WebhooksPage.js +39 -39
  48. package/dist/devportal/pages/WebhooksPage.js.map +1 -1
  49. package/dist/devportal/utils/appDetailTabs.d.ts +73 -0
  50. package/dist/devportal/utils/appDetailTabs.js +62 -0
  51. package/dist/devportal/utils/appDetailTabs.js.map +1 -0
  52. package/dist/devportal/utils/developerNav.d.ts +39 -0
  53. package/dist/devportal/utils/developerNav.js +304 -0
  54. package/dist/devportal/utils/developerNav.js.map +1 -0
  55. package/package.json +2 -2
@@ -1,18 +1,18 @@
1
1
  import { PageLayout as e, PageSection as t } from "../components/PageLayout.js";
2
2
  import { InputField as n, SelectField as r, TagInput as i } from "../components/FormField.js";
3
- import { useCurrentUserAppsQuery as a, useGenerateApiKeyMutation as o } from "../../generated/global-operations.js";
4
- import { NewSecretAlert as s } from "../components/SecretDisplay.js";
3
+ import { NewSecretAlert as a } from "../components/SecretDisplay.js";
4
+ import { useCurrentUserAppsQuery as o, useGenerateApiKeyMutation as s } from "../../generated/global-operations.js";
5
5
  import { Alert as c } from "../components/ConfirmDialog.js";
6
6
  import { useDevportal as l } from "../providers/DevportalProvider.js";
7
7
  import "../components/index.js";
8
- import { PermissionGuard as u } from "../components/PermissionGuard.js";
9
8
  import "../hooks/index.js";
9
+ import { PermissionGuard as u } from "../components/PermissionGuard.js";
10
10
  import { useDevportalEventBus as d } from "../events/index.js";
11
11
  import { executeRecaptcha as f } from "../utils/recaptcha.js";
12
- import { useState as p } from "react";
13
- import { Loader2 as m, Save as h, X as g } from "lucide-react";
14
- import { jsx as _, jsxs as v } from "react/jsx-runtime";
15
- import { useI18n as y } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
12
+ import { useTr as p } from "../utils/i18n.js";
13
+ import { useState as m } from "react";
14
+ import { Loader2 as h, Save as g, X as _ } from "lucide-react";
15
+ import { jsx as v, jsxs as y } from "react/jsx-runtime";
16
16
  import { OptionalFields as b } from "@burdenoff/fe-libs/ui";
17
17
  // src/devportal/pages/CreateApiKeyPage.tsx
18
18
  var x = {
@@ -28,254 +28,251 @@ function S(e) {
28
28
  if (t != null) return new Date(Date.now() + t).toISOString();
29
29
  }
30
30
  var C = () => {
31
- let { t: x } = y(), C = (e, t) => {
32
- let n = x(e);
33
- return n === e ? t : n;
34
- }, { navigate: w, basePath: T } = l(), [E, D] = p(null), [O, k] = p({}), [A, j] = p(null), [M, { loading: N }] = o(), P = d(), { data: F, loading: I } = a({ fetchPolicy: "cache-and-network" }), L = F?.currentUserApps?.apps.map((e) => ({
31
+ let x = p(), { navigate: C, basePath: w } = l(), [T, E] = m(null), [D, O] = m({}), [k, A] = m(null), [j, { loading: M }] = s(), N = d(), { data: P, loading: F } = o({ fetchPolicy: "cache-and-network" }), I = P?.currentUserApps?.apps.map((e) => ({
35
32
  id: e.id,
36
33
  name: e.name,
37
34
  clientId: e.clientId
38
- })) ?? [], [R, z] = p({
35
+ })) ?? [], [L, R] = m({
39
36
  appId: "",
40
37
  name: "",
41
38
  scopes: [],
42
39
  expiresIn: "1year"
43
- }), B = (e) => {
44
- w?.(`${T}${e}`);
45
- }, V = () => {
46
- B("/api-keys");
47
- }, H = (e, t) => {
48
- z((n) => ({
40
+ }), z = (e) => {
41
+ C?.(`${w}${e}`);
42
+ }, B = () => {
43
+ z("/api-keys");
44
+ }, V = (e, t) => {
45
+ R((n) => ({
49
46
  ...n,
50
47
  [e]: t
51
- })), O[e] && k((t) => ({
48
+ })), D[e] && O((t) => ({
52
49
  ...t,
53
50
  [e]: ""
54
- })), A && j(null);
55
- }, U = () => {
51
+ })), k && A(null);
52
+ }, H = () => {
56
53
  let e = {};
57
- return R.appId || (e.appId = C("devportal.selectAppError", "Please select an app to associate this key with")), R.name.trim() || (e.name = C("devportal.apiKeyNameRequired", "API key name is required")), R.scopes.length === 0 && (e.scopes = C("devportal.atLeastOneScopeRequired", "At least one scope is required")), k(e), Object.keys(e).length === 0;
58
- }, W = async (e) => {
59
- if (e.preventDefault(), U()) {
60
- j(null);
54
+ return L.appId || (e.appId = x("devportal.selectAppError", "Please select an app to associate this key with")), L.name.trim() || (e.name = x("devportal.apiKeyNameRequired", "API key name is required")), L.scopes.length === 0 && (e.scopes = x("devportal.atLeastOneScopeRequired", "At least one scope is required")), O(e), Object.keys(e).length === 0;
55
+ }, U = async (e) => {
56
+ if (e.preventDefault(), H()) {
57
+ A(null);
61
58
  try {
62
- let e = S(R.expiresIn), t = await /* @__PURE__ */ f("api_key_create"), n = {
63
- appId: R.appId,
64
- name: R.name.trim(),
65
- scope: R.scopes,
59
+ let e = S(L.expiresIn), t = await /* @__PURE__ */ f("api_key_create"), n = {
60
+ appId: L.appId,
61
+ name: L.name.trim(),
62
+ scope: L.scopes,
66
63
  expiresAt: e,
67
64
  ...t ? { recaptchaToken: t } : {}
68
- }, { data: r } = await M({ variables: { input: n } });
69
- r?.generateAPIKey && (P.emit("devportal.apikey.generated", {
65
+ }, { data: r } = await j({ variables: { input: n } });
66
+ r?.generateAPIKey && (N.emit("devportal.apikey.generated", {
70
67
  keyId: r.generateAPIKey.keyId,
71
- appId: R.appId
72
- }), D({
68
+ appId: L.appId
69
+ }), E({
73
70
  apiKey: r.generateAPIKey.apiKey,
74
71
  apiSecret: r.generateAPIKey.apiSecret,
75
72
  keyId: r.generateAPIKey.keyId
76
73
  }));
77
74
  } catch (e) {
78
- let t = e instanceof Error ? e.message : C("devportal.failedToGenerateApiKey", "Failed to generate API key");
79
- j(t);
75
+ let t = e instanceof Error ? e.message : x("devportal.failedToGenerateApiKey", "Failed to generate API key");
76
+ A(t);
80
77
  }
81
78
  }
82
- }, G = () => {
83
- B("/api-keys");
84
- };
85
- return E ? /* @__PURE__ */ _(e, {
86
- title: "API Key Generated",
87
- description: "Your new API key has been generated successfully",
88
- children: /* @__PURE__ */ v("div", {
79
+ }, W = () => {
80
+ z("/api-keys");
81
+ }, G = [
82
+ {
83
+ value: "30days",
84
+ label: x("devportal.createApiKey.exp30days", "30 days")
85
+ },
86
+ {
87
+ value: "90days",
88
+ label: x("devportal.createApiKey.exp90days", "90 days")
89
+ },
90
+ {
91
+ value: "180days",
92
+ label: x("devportal.createApiKey.exp180days", "180 days")
93
+ },
94
+ {
95
+ value: "1year",
96
+ label: x("devportal.createApiKey.exp1year", "1 year")
97
+ },
98
+ {
99
+ value: "2years",
100
+ label: x("devportal.createApiKey.exp2years", "2 years")
101
+ },
102
+ {
103
+ value: "never",
104
+ label: x("devportal.createApiKey.expNever", "Never expires")
105
+ }
106
+ ];
107
+ return T ? /* @__PURE__ */ v(e, {
108
+ title: x("devportal.createApiKey.generatedTitle", "API Key Generated"),
109
+ description: x("devportal.createApiKey.generatedDescription", "Your new API key has been generated successfully"),
110
+ children: /* @__PURE__ */ y("div", {
89
111
  className: "max-w-2xl space-y-6",
90
112
  children: [
91
- /* @__PURE__ */ _(s, {
92
- title: "API Key & Secret",
93
- secret: E.apiSecret,
94
- description: "Store this secret securely — you won't be able to see it again.",
95
- onDismiss: G
113
+ /* @__PURE__ */ v(a, {
114
+ title: x("devportal.createApiKey.secretTitle", "API Key & Secret"),
115
+ secret: T.apiSecret,
116
+ description: x("devportal.createApiKey.secretDescription", "Store this secret securely — you won't be able to see it again."),
117
+ onDismiss: W
96
118
  }),
97
- /* @__PURE__ */ _(c, {
119
+ /* @__PURE__ */ v(c, {
98
120
  variant: "info",
99
- title: "Usage Instructions",
100
- children: /* @__PURE__ */ v("div", {
121
+ title: x("devportal.createApiKey.usageTitle", "Usage Instructions"),
122
+ children: /* @__PURE__ */ y("div", {
101
123
  className: "mt-2 space-y-2",
102
124
  children: [
103
- /* @__PURE__ */ _("p", { children: "Your API key details:" }),
104
- /* @__PURE__ */ v("div", {
125
+ /* @__PURE__ */ v("p", { children: x("devportal.createApiKey.detailsIntro", "Your API key details:") }),
126
+ /* @__PURE__ */ y("div", {
105
127
  className: "space-y-1",
106
- children: [/* @__PURE__ */ v("div", {
128
+ children: [/* @__PURE__ */ y("div", {
107
129
  className: "flex items-center gap-2",
108
- children: [/* @__PURE__ */ _("span", {
130
+ children: [/* @__PURE__ */ v("span", {
109
131
  className: "text-xs font-medium text-text-secondary",
110
- children: "Key ID:"
111
- }), /* @__PURE__ */ _("code", {
132
+ children: x("devportal.createApiKey.keyIdLabel", "Key ID:")
133
+ }), /* @__PURE__ */ v("code", {
112
134
  className: "rounded bg-bg-sunken px-2 py-0.5 text-sm",
113
- children: E.keyId
135
+ children: T.keyId
114
136
  })]
115
- }), /* @__PURE__ */ v("div", {
137
+ }), /* @__PURE__ */ y("div", {
116
138
  className: "flex items-center gap-2",
117
- children: [/* @__PURE__ */ _("span", {
139
+ children: [/* @__PURE__ */ v("span", {
118
140
  className: "text-xs font-medium text-text-secondary",
119
- children: "API Key:"
120
- }), /* @__PURE__ */ _("code", {
141
+ children: x("devportal.createApiKey.apiKeyLabel", "API Key:")
142
+ }), /* @__PURE__ */ v("code", {
121
143
  className: "rounded bg-bg-sunken px-2 py-0.5 text-sm",
122
- children: E.apiKey
144
+ children: T.apiKey
123
145
  })]
124
146
  })]
125
147
  }),
126
- /* @__PURE__ */ _("p", {
148
+ /* @__PURE__ */ v("p", {
127
149
  className: "text-xs text-text-secondary mt-2",
128
- children: "Use the API key and secret to authenticate your requests."
150
+ children: x("devportal.createApiKey.usageHint", "Use the API key and secret to authenticate your requests.")
129
151
  })
130
152
  ]
131
153
  })
132
154
  }),
133
- /* @__PURE__ */ _("div", {
155
+ /* @__PURE__ */ v("div", {
134
156
  className: "flex gap-3",
135
- children: /* @__PURE__ */ _("button", {
136
- onClick: G,
157
+ children: /* @__PURE__ */ v("button", {
158
+ onClick: W,
137
159
  className: "inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors",
138
- children: "Continue to API Keys"
160
+ children: x("devportal.createApiKey.continue", "Continue to API Keys")
139
161
  })
140
162
  })
141
163
  ]
142
164
  })
143
- }) : /* @__PURE__ */ _(u, {
165
+ }) : /* @__PURE__ */ v(u, {
144
166
  authOnly: !0,
145
- children: /* @__PURE__ */ _(e, {
146
- title: "Generate API Key",
147
- description: "Generate a new API key for programmatic access",
148
- purpose: "Create a key your backend or scripts can use to call the API without a logged-in user. Scope it to one app and the minimum permissions it needs — the secret is shown only once at creation, so copy it somewhere safe.",
167
+ children: /* @__PURE__ */ v(e, {
168
+ title: x("devportal.createApiKey.title", "Generate API Key"),
169
+ description: x("devportal.createApiKey.description", "Generate a new API key for programmatic access"),
170
+ purpose: x("devportal.createApiKey.purpose", "Create a key your backend or scripts can use to call the API without a logged-in user. Scope it to one app and the minimum permissions it needs — the secret is shown only once at creation, so copy it somewhere safe."),
149
171
  backLink: "/api-keys",
150
- backLabel: "Back to API Keys",
151
- children: /* @__PURE__ */ v("form", {
152
- onSubmit: W,
172
+ backLabel: x("devportal.createApiKey.backToKeys", "Back to API Keys"),
173
+ children: /* @__PURE__ */ y("form", {
174
+ onSubmit: U,
153
175
  className: "max-w-3xl space-y-8",
154
176
  children: [
155
- A && /* @__PURE__ */ _(c, {
177
+ k && /* @__PURE__ */ v(c, {
156
178
  variant: "error",
157
- title: "Error Generating API Key",
158
- onDismiss: () => j(null),
159
- children: A
179
+ title: x("devportal.createApiKey.errorTitle", "Error Generating API Key"),
180
+ onDismiss: () => A(null),
181
+ children: k
160
182
  }),
161
- /* @__PURE__ */ _(t, {
162
- title: "Basic Information",
163
- description: "Configure the API key settings",
164
- children: /* @__PURE__ */ v("div", {
183
+ /* @__PURE__ */ v(t, {
184
+ title: x("devportal.createApiKey.basicTitle", "Basic Information"),
185
+ description: x("devportal.createApiKey.basicDescription", "Configure the API key settings"),
186
+ children: /* @__PURE__ */ y("div", {
165
187
  className: "space-y-4",
166
- children: [I ? /* @__PURE__ */ v("div", {
188
+ children: [F ? /* @__PURE__ */ y("div", {
167
189
  className: "flex items-center gap-2 py-2",
168
- children: [/* @__PURE__ */ _(m, { className: "h-4 w-4 animate-spin text-text-secondary" }), /* @__PURE__ */ _("span", {
190
+ children: [/* @__PURE__ */ v(h, { className: "h-4 w-4 animate-spin text-text-secondary" }), /* @__PURE__ */ v("span", {
169
191
  className: "text-sm text-text-secondary",
170
- children: "Loading apps..."
192
+ children: x("devportal.createApiKey.loadingApps", "Loading apps...")
171
193
  })]
172
- }) : L.length === 0 ? /* @__PURE__ */ _(c, {
194
+ }) : I.length === 0 ? /* @__PURE__ */ v(c, {
173
195
  variant: "warning",
174
- title: "No Apps Found",
175
- children: /* @__PURE__ */ v("p", { children: [
176
- "You need to create an OAuth app first before generating API keys.",
196
+ title: x("devportal.createApiKey.noAppsTitle", "No Apps Found"),
197
+ children: /* @__PURE__ */ y("p", { children: [
198
+ x("devportal.createApiKey.noAppsBody", "You need to create an OAuth app first before generating API keys."),
177
199
  " ",
178
- /* @__PURE__ */ _("button", {
200
+ /* @__PURE__ */ v("button", {
179
201
  type: "button",
180
- onClick: () => B("/oauth/new"),
202
+ onClick: () => z("/oauth/new"),
181
203
  className: "text-primary underline hover:no-underline",
182
- children: "Create an app"
204
+ children: x("devportal.createApiKey.createApp", "Create an app")
183
205
  })
184
206
  ] })
185
- }) : /* @__PURE__ */ _(r, {
186
- label: "App",
187
- value: R.appId,
188
- onChange: (e) => H("appId", e.target.value),
189
- options: L.map((e) => ({
207
+ }) : /* @__PURE__ */ v(r, {
208
+ label: x("devportal.createApiKey.appLabel", "App"),
209
+ value: L.appId,
210
+ onChange: (e) => V("appId", e.target.value),
211
+ options: I.map((e) => ({
190
212
  value: e.id,
191
213
  label: `${e.name} (${e.clientId})`
192
214
  })),
193
- placeholder: "Select an app…",
194
- error: O.appId,
215
+ placeholder: x("devportal.createApiKey.appPlaceholder", "Select an app…"),
216
+ error: D.appId,
195
217
  required: !0,
196
- hint: "The OAuth app this API key will be associated with"
197
- }), /* @__PURE__ */ _(n, {
198
- label: "Key Name",
199
- placeholder: "e.g., Production Backend Key",
200
- value: R.name,
201
- onChange: (e) => H("name", e.target.value),
202
- error: O.name,
218
+ hint: x("devportal.createApiKey.appHint", "The OAuth app this API key will be associated with")
219
+ }), /* @__PURE__ */ v(n, {
220
+ label: x("devportal.createApiKey.nameLabel", "Key Name"),
221
+ placeholder: x("devportal.createApiKey.namePlaceholder", "e.g., Production Backend Key"),
222
+ value: L.name,
223
+ onChange: (e) => V("name", e.target.value),
224
+ error: D.name,
203
225
  required: !0,
204
- hint: "A descriptive name to identify this key"
226
+ hint: x("devportal.createApiKey.nameHint", "A descriptive name to identify this key")
205
227
  })]
206
228
  })
207
229
  }),
208
- /* @__PURE__ */ _(t, {
209
- title: "Scopes",
210
- description: "Define what this API key can access",
211
- children: /* @__PURE__ */ _("div", {
230
+ /* @__PURE__ */ v(t, {
231
+ title: x("devportal.createApiKey.scopesTitle", "Scopes"),
232
+ description: x("devportal.createApiKey.scopesDescription", "Define what this API key can access"),
233
+ children: /* @__PURE__ */ v("div", {
212
234
  className: "space-y-4",
213
- children: /* @__PURE__ */ _(i, {
214
- label: "Scopes",
215
- value: R.scopes,
216
- onChange: (e) => H("scopes", e),
217
- placeholder: "e.g., workspace:read, vibecontrols:write",
218
- hint: "Press Enter to add each scope. At least one scope is required.",
219
- error: O.scopes
235
+ children: /* @__PURE__ */ v(i, {
236
+ label: x("devportal.createApiKey.scopesLabel", "Scopes"),
237
+ value: L.scopes,
238
+ onChange: (e) => V("scopes", e),
239
+ placeholder: x("devportal.createApiKey.scopesPlaceholder", "e.g., workspace:read, vibecontrols:write"),
240
+ hint: x("devportal.createApiKey.scopesHint", "Press Enter to add each scope. At least one scope is required."),
241
+ error: D.scopes
220
242
  })
221
243
  })
222
244
  }),
223
- /* @__PURE__ */ _(b, {
224
- title: "Advanced settings",
225
- description: "Rate-limit overrides, allowed origins, scope tuning, and expiration.",
226
- children: /* @__PURE__ */ v(t, {
227
- title: "Key Expiration",
228
- description: "Set when this key should expire",
229
- children: [/* @__PURE__ */ _(r, {
230
- label: "Expires In",
231
- value: R.expiresIn,
232
- onChange: (e) => H("expiresIn", e.target.value),
233
- options: [
234
- {
235
- value: "30days",
236
- label: "30 days"
237
- },
238
- {
239
- value: "90days",
240
- label: "90 days"
241
- },
242
- {
243
- value: "180days",
244
- label: "180 days"
245
- },
246
- {
247
- value: "1year",
248
- label: "1 year"
249
- },
250
- {
251
- value: "2years",
252
- label: "2 years"
253
- },
254
- {
255
- value: "never",
256
- label: "Never expires"
257
- }
258
- ],
259
- hint: "Keys can be revoked at any time regardless of expiration"
260
- }), R.expiresIn === "never" && /* @__PURE__ */ _(c, {
245
+ /* @__PURE__ */ v(b, {
246
+ title: x("devportal.createApiKey.advancedTitle", "Advanced settings"),
247
+ description: x("devportal.createApiKey.advancedDescription", "Rate-limit overrides, allowed origins, scope tuning, and expiration."),
248
+ children: /* @__PURE__ */ y(t, {
249
+ title: x("devportal.createApiKey.expirationTitle", "Key Expiration"),
250
+ description: x("devportal.createApiKey.expirationDescription", "Set when this key should expire"),
251
+ children: [/* @__PURE__ */ v(r, {
252
+ label: x("devportal.createApiKey.expiresInLabel", "Expires In"),
253
+ value: L.expiresIn,
254
+ onChange: (e) => V("expiresIn", e.target.value),
255
+ options: G,
256
+ hint: x("devportal.createApiKey.expiresInHint", "Keys can be revoked at any time regardless of expiration")
257
+ }), L.expiresIn === "never" && /* @__PURE__ */ v(c, {
261
258
  variant: "warning",
262
259
  className: "mt-4",
263
- children: "Non-expiring keys are a security risk. Consider setting an expiration and rotating keys periodically."
260
+ children: x("devportal.createApiKey.neverWarning", "Non-expiring keys are a security risk. Consider setting an expiration and rotating keys periodically.")
264
261
  })]
265
262
  })
266
263
  }),
267
- /* @__PURE__ */ v("div", {
264
+ /* @__PURE__ */ y("div", {
268
265
  className: "flex items-center justify-end gap-3 border-t pt-6",
269
- children: [/* @__PURE__ */ v("button", {
266
+ children: [/* @__PURE__ */ y("button", {
270
267
  type: "button",
271
- onClick: V,
268
+ onClick: B,
272
269
  className: "inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken transition-colors",
273
- children: [/* @__PURE__ */ _(g, { size: 16 }), "Cancel"]
274
- }), /* @__PURE__ */ v("button", {
270
+ children: [/* @__PURE__ */ v(_, { size: 16 }), x("devportal.createApiKey.cancel", "Cancel")]
271
+ }), /* @__PURE__ */ y("button", {
275
272
  type: "submit",
276
- disabled: N || L.length === 0,
273
+ disabled: M || I.length === 0,
277
274
  className: "inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors disabled:opacity-50",
278
- children: [/* @__PURE__ */ _(h, { size: 16 }), N ? "Generating..." : "Generate API Key"]
275
+ children: [/* @__PURE__ */ v(g, { size: 16 }), M ? x("devportal.createApiKey.generating", "Generating...") : x("devportal.createApiKey.submit", "Generate API Key")]
279
276
  })]
280
277
  })
281
278
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"CreateApiKeyPage.js","names":["EXPIRATION_DURATIONS","computeExpiresAt","duration","expiresIn","CreateApiKeyPage","useI18n","tr","value","t","key","fallback","useDevportal","newApiKey","setNewApiKey","useState","errors","setErrors","mutationError","setMutationError","generateApiKey","isSubmitting","useGenerateApiKeyMutation","bus","useDevportalEventBus","appsData","appsLoading","useCurrentUserAppsQuery","apps","app","formData","setFormData","handleNavigate","basePath","path","handleCancel","updateField","prev","field","validateForm","newErrors","handleSubmit","expiresAt","recaptchaToken","input","data","err","message","handleKeyDismiss","PageLayout","NewSecretAlert","Alert","PermissionGuard","PageSection","Loader2","SelectField","InputField","TagInput","values","OptionalFields","X","Save"],"sources":["../../../src/devportal/pages/CreateApiKeyPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport { Save, X, Loader2 } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { OptionalFields } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { InputField, SelectField, TagInput } from \"../components/FormField\";\nimport { NewSecretAlert } from \"../components/SecretDisplay\";\nimport { Alert } from \"../components/ConfirmDialog\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { useGenerateApiKeyMutation, useCurrentUserAppsQuery } from \"../hooks\";\nimport { useDevportalEventBus } from \"../events\";\nimport { executeRecaptcha } from \"../utils/recaptcha\";\n\ninterface CreateApiKeyFormData {\n appId: string;\n name: string;\n scopes: string[];\n expiresIn: string;\n}\n\nconst EXPIRATION_DURATIONS: Record<string, number | null> = {\n \"30days\": 30 * 24 * 60 * 60 * 1000,\n \"90days\": 90 * 24 * 60 * 60 * 1000,\n \"180days\": 180 * 24 * 60 * 60 * 1000,\n \"1year\": 365 * 24 * 60 * 60 * 1000,\n \"2years\": 2 * 365 * 24 * 60 * 60 * 1000,\n never: null,\n};\n\nfunction computeExpiresAt(expiresIn: string): string | undefined {\n const duration = EXPIRATION_DURATIONS[expiresIn];\n if (duration == null) return undefined;\n return new Date(Date.now() + duration).toISOString();\n}\n\n/**\n * Page for generating a new API key via auth-svc.\n *\n * Previously created API keys through devportal-svc per application;\n * now generates keys through auth-svc's generateAPIKey mutation.\n * The user selects which OAuth app to associate the key with.\n */\nexport const CreateApiKeyPage: FC = () => {\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 const { navigate, basePath } = useDevportal();\n const [newApiKey, setNewApiKey] = useState<{\n apiKey: string;\n apiSecret: string;\n keyId: string;\n } | null>(null);\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [mutationError, setMutationError] = useState<string | null>(null);\n\n const [generateApiKey, { loading: isSubmitting }] =\n useGenerateApiKeyMutation();\n const bus = useDevportalEventBus();\n\n // Fetch user's apps to populate the app selector\n const { data: appsData, loading: appsLoading } = useCurrentUserAppsQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const apps: Array<{ id: string; name: string; clientId: string }> =\n appsData?.currentUserApps?.apps.map(\n (app: { id: string; name: string; clientId: string }) => ({\n id: app.id,\n name: app.name,\n clientId: app.clientId,\n }),\n ) ?? [];\n\n const [formData, setFormData] = useState<CreateApiKeyFormData>({\n appId: \"\",\n name: \"\",\n scopes: [],\n expiresIn: \"1year\",\n });\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const handleCancel = () => {\n handleNavigate(\"/api-keys\");\n };\n\n const updateField = <K extends keyof CreateApiKeyFormData>(\n field: K,\n value: CreateApiKeyFormData[K],\n ) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n if (errors[field]) {\n setErrors((prev) => ({ ...prev, [field]: \"\" }));\n }\n if (mutationError) {\n setMutationError(null);\n }\n };\n\n const validateForm = (): boolean => {\n const newErrors: Record<string, string> = {};\n\n if (!formData.appId) {\n newErrors.appId = tr(\n \"devportal.selectAppError\",\n \"Please select an app to associate this key with\",\n );\n }\n\n if (!formData.name.trim()) {\n newErrors.name = tr(\n \"devportal.apiKeyNameRequired\",\n \"API key name is required\",\n );\n }\n\n if (formData.scopes.length === 0) {\n newErrors.scopes = tr(\n \"devportal.atLeastOneScopeRequired\",\n \"At least one scope is required\",\n );\n }\n\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validateForm()) return;\n\n setMutationError(null);\n\n try {\n const expiresAt = computeExpiresAt(formData.expiresIn);\n const recaptchaToken = await executeRecaptcha(\"api_key_create\");\n const input = {\n appId: formData.appId,\n name: formData.name.trim(),\n scope: formData.scopes,\n expiresAt,\n ...(recaptchaToken ? { recaptchaToken } : {}),\n };\n\n const { data } = await generateApiKey({\n variables: {\n input,\n },\n });\n\n if (data?.generateAPIKey) {\n bus.emit(\"devportal.apikey.generated\", {\n keyId: data.generateAPIKey.keyId,\n appId: formData.appId,\n });\n setNewApiKey({\n apiKey: data.generateAPIKey.apiKey,\n apiSecret: data.generateAPIKey.apiSecret,\n keyId: data.generateAPIKey.keyId,\n });\n }\n } catch (err) {\n const message =\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToGenerateApiKey\",\n \"Failed to generate API key\",\n );\n setMutationError(message);\n }\n };\n\n const handleKeyDismiss = () => {\n handleNavigate(\"/api-keys\");\n };\n\n const expirationOptions = [\n { value: \"30days\", label: \"30 days\" },\n { value: \"90days\", label: \"90 days\" },\n { value: \"180days\", label: \"180 days\" },\n { value: \"1year\", label: \"1 year\" },\n { value: \"2years\", label: \"2 years\" },\n { value: \"never\", label: \"Never expires\" },\n ];\n\n if (newApiKey) {\n return (\n <PageLayout\n title=\"API Key Generated\"\n description=\"Your new API key has been generated successfully\"\n >\n <div className=\"max-w-2xl space-y-6\">\n <NewSecretAlert\n title=\"API Key & Secret\"\n secret={newApiKey.apiSecret}\n description=\"Store this secret securely — you won't be able to see it again.\"\n onDismiss={handleKeyDismiss}\n />\n\n <Alert variant=\"info\" title=\"Usage Instructions\">\n <div className=\"mt-2 space-y-2\">\n <p>Your API key details:</p>\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs font-medium text-text-secondary\">\n Key ID:\n </span>\n <code className=\"rounded bg-bg-sunken px-2 py-0.5 text-sm\">\n {newApiKey.keyId}\n </code>\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs font-medium text-text-secondary\">\n API Key:\n </span>\n <code className=\"rounded bg-bg-sunken px-2 py-0.5 text-sm\">\n {newApiKey.apiKey}\n </code>\n </div>\n </div>\n <p className=\"text-xs text-text-secondary mt-2\">\n Use the API key and secret to authenticate your requests.\n </p>\n </div>\n </Alert>\n\n <div className=\"flex gap-3\">\n <button\n onClick={handleKeyDismiss}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n Continue to API Keys\n </button>\n </div>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title=\"Generate API Key\"\n description=\"Generate a new API key for programmatic access\"\n purpose=\"Create a key your backend or scripts can use to call the API without a logged-in user. Scope it to one app and the minimum permissions it needs — the secret is shown only once at creation, so copy it somewhere safe.\"\n backLink=\"/api-keys\"\n backLabel=\"Back to API Keys\"\n >\n <form onSubmit={handleSubmit} className=\"max-w-3xl space-y-8\">\n {/* Mutation error */}\n {mutationError && (\n <Alert\n variant=\"error\"\n title=\"Error Generating API Key\"\n onDismiss={() => setMutationError(null)}\n >\n {mutationError}\n </Alert>\n )}\n\n {/* Basic Information */}\n <PageSection\n title=\"Basic Information\"\n description=\"Configure the API key settings\"\n >\n <div className=\"space-y-4\">\n {/* App Selector */}\n {appsLoading ? (\n <div className=\"flex items-center gap-2 py-2\">\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n <span className=\"text-sm text-text-secondary\">\n Loading apps...\n </span>\n </div>\n ) : apps.length === 0 ? (\n <Alert variant=\"warning\" title=\"No Apps Found\">\n <p>\n You need to create an OAuth app first before generating API\n keys.{\" \"}\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/oauth/new\")}\n className=\"text-primary underline hover:no-underline\"\n >\n Create an app\n </button>\n </p>\n </Alert>\n ) : (\n <SelectField\n label=\"App\"\n value={formData.appId}\n onChange={(e) => updateField(\"appId\", e.target.value)}\n options={apps.map((app) => ({\n value: app.id,\n label: `${app.name} (${app.clientId})`,\n }))}\n placeholder=\"Select an app…\"\n error={errors.appId}\n required\n hint=\"The OAuth app this API key will be associated with\"\n />\n )}\n\n <InputField\n label=\"Key Name\"\n placeholder=\"e.g., Production Backend Key\"\n value={formData.name}\n onChange={(e) => updateField(\"name\", e.target.value)}\n error={errors.name}\n required\n hint=\"A descriptive name to identify this key\"\n />\n </div>\n </PageSection>\n\n {/* Scopes */}\n <PageSection\n title=\"Scopes\"\n description=\"Define what this API key can access\"\n >\n <div className=\"space-y-4\">\n <TagInput\n label=\"Scopes\"\n value={formData.scopes}\n onChange={(values) => updateField(\"scopes\", values)}\n placeholder=\"e.g., workspace:read, vibecontrols:write\"\n hint=\"Press Enter to add each scope. At least one scope is required.\"\n error={errors.scopes}\n />\n </div>\n </PageSection>\n\n {/* Advanced: rate-limit overrides / allowed origins / scope tuning / expiration — collapse the long-tail knobs */}\n <OptionalFields\n title=\"Advanced settings\"\n description=\"Rate-limit overrides, allowed origins, scope tuning, and expiration.\"\n >\n <PageSection\n title=\"Key Expiration\"\n description=\"Set when this key should expire\"\n >\n <SelectField\n label=\"Expires In\"\n value={formData.expiresIn}\n onChange={(e) => updateField(\"expiresIn\", e.target.value)}\n options={expirationOptions}\n hint=\"Keys can be revoked at any time regardless of expiration\"\n />\n\n {formData.expiresIn === \"never\" && (\n <Alert variant=\"warning\" className=\"mt-4\">\n Non-expiring keys are a security risk. Consider setting an\n expiration and rotating keys periodically.\n </Alert>\n )}\n </PageSection>\n </OptionalFields>\n\n {/* Actions */}\n <div className=\"flex items-center justify-end gap-3 border-t pt-6\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken transition-colors\"\n >\n <X size={16} />\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting || apps.length === 0}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors disabled:opacity-50\"\n >\n <Save size={16} />\n {isSubmitting ? \"Generating...\" : \"Generate API Key\"}\n </button>\n </div>\n </form>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAsBA,IAAMA,IAAsD;CAC1D,UAAU;CACV,UAAU;CACV,WAAW;CACX,SAAS;CACT,UAAU;CACV,OAAO;AACT;AAEA,SAASC,EAAiB,GAAuC;CAC/D,IAAMC,IAAWF,EAAqBG;CACtC,IAAID,KAAY,MAChB,OAAO,IAAI,KAAK,KAAK,IAAI,IAAIA,CAAQ,CAAC,CAAC,YAAY;AACrD;AASA,IAAaE,UAA6B;CACxC,IAAM,EAAE,SAAMC,EAAQ,GAChBC,KAAM,GAAa,MAA6B;EACpD,IAAMC,IAAQC,EAAEC,CAAG;EACnB,OAAOF,MAAUE,IAAMC,IAAWH;CACpC,GACM,EAAE,aAAU,gBAAaI,EAAa,GACtC,CAACC,GAAWC,KAAgBC,EAIxB,IAAI,GACR,CAACC,GAAQC,KAAaF,EAAiC,CAAC,CAAC,GACzD,CAACG,GAAeC,KAAoBJ,EAAwB,IAAI,GAEhE,CAACK,GAAgB,EAAE,SAASC,OAChCC,EAA0B,GACtBC,IAAMC,EAAqB,GAG3B,EAAE,MAAMC,GAAU,SAASC,MAAgBC,EAAwB,EACvE,aAAa,oBACf,CAAC,GAEKC,IACJH,GAAU,iBAAiB,KAAK,KAC7B,OAAyD;EACxD,IAAII,EAAI;EACR,MAAMA,EAAI;EACV,UAAUA,EAAI;CAChB,EACF,KAAK,CAAC,GAEF,CAACC,GAAUC,KAAehB,EAA+B;EAC7D,OAAO;EACP,MAAM;EACN,QAAQ,CAAC;EACT,WAAW;CACb,CAAC,GAEKiB,KAAkB,MAAiB;EACvC,IAAW,GAAGC,IAAWC,GAAM;CACjC,GAEMC,UAAqB;EACzB,EAAe,WAAW;CAC5B,GAEMC,KACJ,GACA,MACG;EAKH,AAJAL,GAAa,OAAU;GAAE,GAAGM;IAAOC,IAAQ9B;EAAM,EAAE,GAC/CQ,EAAOsB,MACTrB,GAAW,OAAU;GAAE,GAAGoB;IAAOC,IAAQ;EAAG,EAAE,GAE5CpB,KACFC,EAAiB,IAAI;CAEzB,GAEMoB,UAA8B;EAClC,IAAMC,IAAoC,CAAC;EAwB3C,OAtBKV,EAAS,UACZ,EAAU,QAAQvB,EAChB,4BACA,iDACF,IAGGuB,EAAS,KAAK,KAAK,MACtB,EAAU,OAAOvB,EACf,gCACA,0BACF,IAGEuB,EAAS,OAAO,WAAW,MAC7B,EAAU,SAASvB,EACjB,qCACA,gCACF,IAGFU,EAAUuB,CAAS,GACZ,OAAO,KAAKA,CAAS,CAAC,CAAC,WAAW;CAC3C,GAEMC,IAAe,OAAO,MAAuB;EAGjD,IAFA,EAAE,eAAe,GAEZF,EAAa,GAElB;KAAiB,IAAI;GAErB,IAAI;IACF,IAAMG,IAAYxC,EAAiB4B,EAAS,SAAS,GAC/Ca,IAAiB,MAAM,kBAAiB,gBAAgB,GACxDC,IAAQ;KACZ,OAAOd,EAAS;KAChB,MAAMA,EAAS,KAAK,KAAK;KACzB,OAAOA,EAAS;KAChB;KACA,GAAIa,IAAiB,EAAE,kBAAe,IAAI,CAAC;IAC7C,GAEM,EAAE,YAAS,MAAMvB,EAAe,EACpC,WAAW,EACT,SACF,EACF,CAAC;IAED,AAAIyB,GAAM,mBACRtB,EAAI,KAAK,8BAA8B;KACrC,OAAOsB,EAAK,eAAe;KAC3B,OAAOf,EAAS;IAClB,CAAC,GACDhB,EAAa;KACX,QAAQ+B,EAAK,eAAe;KAC5B,WAAWA,EAAK,eAAe;KAC/B,OAAOA,EAAK,eAAe;IAC7B,CAAC;GAEL,SAASC,GAAK;IACZ,IAAMC,IACJD,aAAe,QACXA,EAAI,UACJvC,EACE,oCACA,4BACF;IACN,EAAiBwC,CAAO;GAC1B;EAvCqB;CAwCvB,GAEMC,UAAyB;EAC7B,EAAe,WAAW;CAC5B;CAiEA,OAtDInC,IAEA,kBAACoC,GAAD;EACE,OAAM;EACN,aAAY;EAEZ,UAAA,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACE,kBAACC,GAAD;KACE,OAAM;KACN,QAAQrC,EAAU;KAClB,aAAY;KACZ,WAAWmC;IACZ,CAAA;IAED,kBAACG,GAAD;KAAO,SAAQ;KAAO,OAAM;KAC1B,UAAA,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA;OACE,kBAAC,KAAD,EAAA,UAAG,wBAAwB,CAAA;OAC3B,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,kBAAC,QAAD;UAAM,WAAU;UAA0C,UAAA;SAEpD,CAAA,GACN,kBAAC,QAAD;UAAM,WAAU;UACb,UAAAtC,EAAU;SACP,CAAA,CACH;QACL,CAAA,GAAA,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,kBAAC,QAAD;UAAM,WAAU;UAA0C,UAAA;SAEpD,CAAA,GACN,kBAAC,QAAD;UAAM,WAAU;UACb,UAAAA,EAAU;SACP,CAAA,CACH;QACF,CAAA,CAAA;;OACL,kBAAC,KAAD;QAAG,WAAU;QAAmC,UAAA;OAE7C,CAAA;MACA;;IACA,CAAA;IAEP,kBAAC,OAAD;KAAK,WAAU;KACb,UAAA,kBAAC,UAAD;MACE,SAASmC;MACT,WAAU;MACX,UAAA;KAEO,CAAA;IACL,CAAA;GACF;;CACK,CAAA,IAKd,kBAACI,GAAD;EAAiB,UAAA;EACf,UAAA,kBAACH,GAAD;GACE,OAAM;GACN,aAAY;GACZ,SAAQ;GACR,UAAS;GACT,WAAU;GAEV,UAAA,kBAAC,QAAD;IAAM,UAAUR;IAAc,WAAU;IAAxC,UAAA;KAEGvB,KACC,kBAACiC,GAAD;MACE,SAAQ;MACR,OAAM;MACN,iBAAiBhC,EAAiB,IAAI;MAErC,UAAAD;KACI,CAAA;KAIT,kBAACmC,GAAD;MACE,OAAM;MACN,aAAY;MAEZ,UAAA,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CAEG3B,IACC,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,kBAAC4B,GAAD,EAAS,WAAU,2CAA4C,CAAA,GAC/D,kBAAC,QAAD;SAAM,WAAU;SAA8B,UAAA;QAExC,CAAA,CACH;OACH,CAAA,IAAA1B,EAAK,WAAW,IAClB,kBAACuB,GAAD;QAAO,SAAQ;QAAU,OAAM;QAC7B,UAAA,kBAAC,KAAD,EAAA,UAAA;SAAG;SAEK;SACN,kBAAC,UAAD;UACE,MAAK;UACL,eAAenB,EAAe,YAAY;UAC1C,WAAU;UACX,UAAA;SAEO,CAAA;QACP,EAAA,CAAA;OACE,CAAA,IAEP,kBAACuB,GAAD;QACE,OAAM;QACN,OAAOzB,EAAS;QAChB,WAAW,MAAMM,EAAY,SAAS,EAAE,OAAO,KAAK;QACpD,SAASR,EAAK,KAAK,OAAS;SAC1B,OAAOC,EAAI;SACX,OAAO,GAAGA,EAAI,KAAK,IAAIA,EAAI,SAAS;QACtC,EAAE;QACF,aAAY;QACZ,OAAOb,EAAO;QACd,UAAA;QACA,MAAK;OACN,CAAA,GAGH,kBAACwC,GAAD;QACE,OAAM;QACN,aAAY;QACZ,OAAO1B,EAAS;QAChB,WAAW,MAAMM,EAAY,QAAQ,EAAE,OAAO,KAAK;QACnD,OAAOpB,EAAO;QACd,UAAA;QACA,MAAK;OACN,CAAA,CACE;;KACM,CAAA;KAGb,kBAACqC,GAAD;MACE,OAAM;MACN,aAAY;MAEZ,UAAA,kBAAC,OAAD;OAAK,WAAU;OACb,UAAA,kBAACI,GAAD;QACE,OAAM;QACN,OAAO3B,EAAS;QAChB,WAAW,MAAWM,EAAY,UAAUsB,CAAM;QAClD,aAAY;QACZ,MAAK;QACL,OAAO1C,EAAO;OACf,CAAA;MACE,CAAA;KACM,CAAA;KAGb,kBAAC2C,GAAD;MACE,OAAM;MACN,aAAY;MAEZ,UAAA,kBAACN,GAAD;OACE,OAAM;OACN,aAAY;OAFd,UAAA,CAIE,kBAACE,GAAD;QACE,OAAM;QACN,OAAOzB,EAAS;QAChB,WAAW,MAAMM,EAAY,aAAa,EAAE,OAAO,KAAK;QACxD,SAAS;SAzKrB;UAAE,OAAO;UAAU,OAAO;SAAU;SACpC;UAAE,OAAO;UAAU,OAAO;SAAU;SACpC;UAAE,OAAO;UAAW,OAAO;SAAW;SACtC;UAAE,OAAO;UAAS,OAAO;SAAS;SAClC;UAAE,OAAO;UAAU,OAAO;SAAU;SACpC;UAAE,OAAO;UAAS,OAAO;SAAgB;QAoKpB;QACT,MAAK;OACN,CAAA,GAEAN,EAAS,cAAc,WACtB,kBAACqB,GAAD;QAAO,SAAQ;QAAU,WAAU;QAAO,UAAA;OAGnC,CAAA,CAEE;;KACC,CAAA;KAGhB,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,UAAD;OACE,MAAK;OACL,SAAShB;OACT,WAAU;OAHZ,UAAA,CAKE,kBAACyB,GAAD,EAAG,MAAM,GAAK,CAAA,GAAC,QAET;MACR,CAAA,GAAA,kBAAC,UAAD;OACE,MAAK;OACL,UAAUvC,KAAgBO,EAAK,WAAW;OAC1C,WAAU;OAHZ,UAAA,CAKE,kBAACiC,GAAD,EAAM,MAAM,GAAK,CAAA,GAChBxC,IAAe,kBAAkB,kBAC5B;MACL,CAAA,CAAA;;IACD;;EACI,CAAA;CACG,CAAA;AAErB"}
1
+ {"version":3,"file":"CreateApiKeyPage.js","names":["EXPIRATION_DURATIONS","computeExpiresAt","duration","expiresIn","CreateApiKeyPage","tr","useTr","useDevportal","newApiKey","setNewApiKey","useState","errors","setErrors","mutationError","setMutationError","generateApiKey","isSubmitting","useGenerateApiKeyMutation","bus","useDevportalEventBus","appsData","appsLoading","useCurrentUserAppsQuery","apps","app","formData","setFormData","handleNavigate","basePath","path","handleCancel","updateField","prev","field","value","validateForm","newErrors","handleSubmit","expiresAt","recaptchaToken","input","data","err","message","handleKeyDismiss","expirationOptions","PageLayout","NewSecretAlert","Alert","PermissionGuard","PageSection","Loader2","SelectField","InputField","TagInput","values","OptionalFields","X","Save"],"sources":["../../../src/devportal/pages/CreateApiKeyPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport { Save, X, Loader2 } from \"lucide-react\";\nimport { OptionalFields } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { InputField, SelectField, TagInput } from \"../components/FormField\";\nimport { NewSecretAlert } from \"../components/SecretDisplay\";\nimport { Alert } from \"../components/ConfirmDialog\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { useGenerateApiKeyMutation, useCurrentUserAppsQuery } from \"../hooks\";\nimport { useDevportalEventBus } from \"../events\";\nimport { executeRecaptcha } from \"../utils/recaptcha\";\nimport { useTr } from \"../utils/i18n\";\n\ninterface CreateApiKeyFormData {\n appId: string;\n name: string;\n scopes: string[];\n expiresIn: string;\n}\n\nconst EXPIRATION_DURATIONS: Record<string, number | null> = {\n \"30days\": 30 * 24 * 60 * 60 * 1000,\n \"90days\": 90 * 24 * 60 * 60 * 1000,\n \"180days\": 180 * 24 * 60 * 60 * 1000,\n \"1year\": 365 * 24 * 60 * 60 * 1000,\n \"2years\": 2 * 365 * 24 * 60 * 60 * 1000,\n never: null,\n};\n\nfunction computeExpiresAt(expiresIn: string): string | undefined {\n const duration = EXPIRATION_DURATIONS[expiresIn];\n if (duration == null) return undefined;\n return new Date(Date.now() + duration).toISOString();\n}\n\n/**\n * Page for generating a new API key via auth-svc.\n *\n * Previously created API keys through devportal-svc per application;\n * now generates keys through auth-svc's generateAPIKey mutation.\n * The user selects which OAuth app to associate the key with.\n */\nexport const CreateApiKeyPage: FC = () => {\n const tr = useTr();\n const { navigate, basePath } = useDevportal();\n const [newApiKey, setNewApiKey] = useState<{\n apiKey: string;\n apiSecret: string;\n keyId: string;\n } | null>(null);\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [mutationError, setMutationError] = useState<string | null>(null);\n\n const [generateApiKey, { loading: isSubmitting }] =\n useGenerateApiKeyMutation();\n const bus = useDevportalEventBus();\n\n // Fetch user's apps to populate the app selector\n const { data: appsData, loading: appsLoading } = useCurrentUserAppsQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const apps: Array<{ id: string; name: string; clientId: string }> =\n appsData?.currentUserApps?.apps.map(\n (app: { id: string; name: string; clientId: string }) => ({\n id: app.id,\n name: app.name,\n clientId: app.clientId,\n }),\n ) ?? [];\n\n const [formData, setFormData] = useState<CreateApiKeyFormData>({\n appId: \"\",\n name: \"\",\n scopes: [],\n expiresIn: \"1year\",\n });\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const handleCancel = () => {\n handleNavigate(\"/api-keys\");\n };\n\n const updateField = <K extends keyof CreateApiKeyFormData>(\n field: K,\n value: CreateApiKeyFormData[K],\n ) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n if (errors[field]) {\n setErrors((prev) => ({ ...prev, [field]: \"\" }));\n }\n if (mutationError) {\n setMutationError(null);\n }\n };\n\n const validateForm = (): boolean => {\n const newErrors: Record<string, string> = {};\n\n if (!formData.appId) {\n newErrors.appId = tr(\n \"devportal.selectAppError\",\n \"Please select an app to associate this key with\",\n );\n }\n\n if (!formData.name.trim()) {\n newErrors.name = tr(\n \"devportal.apiKeyNameRequired\",\n \"API key name is required\",\n );\n }\n\n if (formData.scopes.length === 0) {\n newErrors.scopes = tr(\n \"devportal.atLeastOneScopeRequired\",\n \"At least one scope is required\",\n );\n }\n\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validateForm()) return;\n\n setMutationError(null);\n\n try {\n const expiresAt = computeExpiresAt(formData.expiresIn);\n const recaptchaToken = await executeRecaptcha(\"api_key_create\");\n const input = {\n appId: formData.appId,\n name: formData.name.trim(),\n scope: formData.scopes,\n expiresAt,\n ...(recaptchaToken ? { recaptchaToken } : {}),\n };\n\n const { data } = await generateApiKey({\n variables: {\n input,\n },\n });\n\n if (data?.generateAPIKey) {\n bus.emit(\"devportal.apikey.generated\", {\n keyId: data.generateAPIKey.keyId,\n appId: formData.appId,\n });\n setNewApiKey({\n apiKey: data.generateAPIKey.apiKey,\n apiSecret: data.generateAPIKey.apiSecret,\n keyId: data.generateAPIKey.keyId,\n });\n }\n } catch (err) {\n const message =\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToGenerateApiKey\",\n \"Failed to generate API key\",\n );\n setMutationError(message);\n }\n };\n\n const handleKeyDismiss = () => {\n handleNavigate(\"/api-keys\");\n };\n\n const expirationOptions = [\n {\n value: \"30days\",\n label: tr(\"devportal.createApiKey.exp30days\", \"30 days\"),\n },\n {\n value: \"90days\",\n label: tr(\"devportal.createApiKey.exp90days\", \"90 days\"),\n },\n {\n value: \"180days\",\n label: tr(\"devportal.createApiKey.exp180days\", \"180 days\"),\n },\n { value: \"1year\", label: tr(\"devportal.createApiKey.exp1year\", \"1 year\") },\n {\n value: \"2years\",\n label: tr(\"devportal.createApiKey.exp2years\", \"2 years\"),\n },\n {\n value: \"never\",\n label: tr(\"devportal.createApiKey.expNever\", \"Never expires\"),\n },\n ];\n\n if (newApiKey) {\n return (\n <PageLayout\n title={tr(\"devportal.createApiKey.generatedTitle\", \"API Key Generated\")}\n description={tr(\n \"devportal.createApiKey.generatedDescription\",\n \"Your new API key has been generated successfully\",\n )}\n >\n <div className=\"max-w-2xl space-y-6\">\n <NewSecretAlert\n title={tr(\"devportal.createApiKey.secretTitle\", \"API Key & Secret\")}\n secret={newApiKey.apiSecret}\n description={tr(\n \"devportal.createApiKey.secretDescription\",\n \"Store this secret securely — you won't be able to see it again.\",\n )}\n onDismiss={handleKeyDismiss}\n />\n\n <Alert\n variant=\"info\"\n title={tr(\n \"devportal.createApiKey.usageTitle\",\n \"Usage Instructions\",\n )}\n >\n <div className=\"mt-2 space-y-2\">\n <p>\n {tr(\n \"devportal.createApiKey.detailsIntro\",\n \"Your API key details:\",\n )}\n </p>\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs font-medium text-text-secondary\">\n {tr(\"devportal.createApiKey.keyIdLabel\", \"Key ID:\")}\n </span>\n <code className=\"rounded bg-bg-sunken px-2 py-0.5 text-sm\">\n {newApiKey.keyId}\n </code>\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs font-medium text-text-secondary\">\n {tr(\"devportal.createApiKey.apiKeyLabel\", \"API Key:\")}\n </span>\n <code className=\"rounded bg-bg-sunken px-2 py-0.5 text-sm\">\n {newApiKey.apiKey}\n </code>\n </div>\n </div>\n <p className=\"text-xs text-text-secondary mt-2\">\n {tr(\n \"devportal.createApiKey.usageHint\",\n \"Use the API key and secret to authenticate your requests.\",\n )}\n </p>\n </div>\n </Alert>\n\n <div className=\"flex gap-3\">\n <button\n onClick={handleKeyDismiss}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n {tr(\"devportal.createApiKey.continue\", \"Continue to API Keys\")}\n </button>\n </div>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.createApiKey.title\", \"Generate API Key\")}\n description={tr(\n \"devportal.createApiKey.description\",\n \"Generate a new API key for programmatic access\",\n )}\n purpose={tr(\n \"devportal.createApiKey.purpose\",\n \"Create a key your backend or scripts can use to call the API without a logged-in user. Scope it to one app and the minimum permissions it needs — the secret is shown only once at creation, so copy it somewhere safe.\",\n )}\n backLink=\"/api-keys\"\n backLabel={tr(\"devportal.createApiKey.backToKeys\", \"Back to API Keys\")}\n >\n <form onSubmit={handleSubmit} className=\"max-w-3xl space-y-8\">\n {/* Mutation error */}\n {mutationError && (\n <Alert\n variant=\"error\"\n title={tr(\n \"devportal.createApiKey.errorTitle\",\n \"Error Generating API Key\",\n )}\n onDismiss={() => setMutationError(null)}\n >\n {mutationError}\n </Alert>\n )}\n\n {/* Basic Information */}\n <PageSection\n title={tr(\"devportal.createApiKey.basicTitle\", \"Basic Information\")}\n description={tr(\n \"devportal.createApiKey.basicDescription\",\n \"Configure the API key settings\",\n )}\n >\n <div className=\"space-y-4\">\n {/* App Selector */}\n {appsLoading ? (\n <div className=\"flex items-center gap-2 py-2\">\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n <span className=\"text-sm text-text-secondary\">\n {tr(\n \"devportal.createApiKey.loadingApps\",\n \"Loading apps...\",\n )}\n </span>\n </div>\n ) : apps.length === 0 ? (\n <Alert\n variant=\"warning\"\n title={tr(\n \"devportal.createApiKey.noAppsTitle\",\n \"No Apps Found\",\n )}\n >\n <p>\n {tr(\n \"devportal.createApiKey.noAppsBody\",\n \"You need to create an OAuth app first before generating API keys.\",\n )}{\" \"}\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/oauth/new\")}\n className=\"text-primary underline hover:no-underline\"\n >\n {tr(\"devportal.createApiKey.createApp\", \"Create an app\")}\n </button>\n </p>\n </Alert>\n ) : (\n <SelectField\n label={tr(\"devportal.createApiKey.appLabel\", \"App\")}\n value={formData.appId}\n onChange={(e) => updateField(\"appId\", e.target.value)}\n options={apps.map((app) => ({\n value: app.id,\n label: `${app.name} (${app.clientId})`,\n }))}\n placeholder={tr(\n \"devportal.createApiKey.appPlaceholder\",\n \"Select an app…\",\n )}\n error={errors.appId}\n required\n hint={tr(\n \"devportal.createApiKey.appHint\",\n \"The OAuth app this API key will be associated with\",\n )}\n />\n )}\n\n <InputField\n label={tr(\"devportal.createApiKey.nameLabel\", \"Key Name\")}\n placeholder={tr(\n \"devportal.createApiKey.namePlaceholder\",\n \"e.g., Production Backend Key\",\n )}\n value={formData.name}\n onChange={(e) => updateField(\"name\", e.target.value)}\n error={errors.name}\n required\n hint={tr(\n \"devportal.createApiKey.nameHint\",\n \"A descriptive name to identify this key\",\n )}\n />\n </div>\n </PageSection>\n\n {/* Scopes */}\n <PageSection\n title={tr(\"devportal.createApiKey.scopesTitle\", \"Scopes\")}\n description={tr(\n \"devportal.createApiKey.scopesDescription\",\n \"Define what this API key can access\",\n )}\n >\n <div className=\"space-y-4\">\n <TagInput\n label={tr(\"devportal.createApiKey.scopesLabel\", \"Scopes\")}\n value={formData.scopes}\n onChange={(values) => updateField(\"scopes\", values)}\n placeholder={tr(\n \"devportal.createApiKey.scopesPlaceholder\",\n \"e.g., workspace:read, vibecontrols:write\",\n )}\n hint={tr(\n \"devportal.createApiKey.scopesHint\",\n \"Press Enter to add each scope. At least one scope is required.\",\n )}\n error={errors.scopes}\n />\n </div>\n </PageSection>\n\n {/* Advanced: rate-limit overrides / allowed origins / scope tuning / expiration — collapse the long-tail knobs */}\n <OptionalFields\n title={tr(\n \"devportal.createApiKey.advancedTitle\",\n \"Advanced settings\",\n )}\n description={tr(\n \"devportal.createApiKey.advancedDescription\",\n \"Rate-limit overrides, allowed origins, scope tuning, and expiration.\",\n )}\n >\n <PageSection\n title={tr(\n \"devportal.createApiKey.expirationTitle\",\n \"Key Expiration\",\n )}\n description={tr(\n \"devportal.createApiKey.expirationDescription\",\n \"Set when this key should expire\",\n )}\n >\n <SelectField\n label={tr(\n \"devportal.createApiKey.expiresInLabel\",\n \"Expires In\",\n )}\n value={formData.expiresIn}\n onChange={(e) => updateField(\"expiresIn\", e.target.value)}\n options={expirationOptions}\n hint={tr(\n \"devportal.createApiKey.expiresInHint\",\n \"Keys can be revoked at any time regardless of expiration\",\n )}\n />\n\n {formData.expiresIn === \"never\" && (\n <Alert variant=\"warning\" className=\"mt-4\">\n {tr(\n \"devportal.createApiKey.neverWarning\",\n \"Non-expiring keys are a security risk. Consider setting an expiration and rotating keys periodically.\",\n )}\n </Alert>\n )}\n </PageSection>\n </OptionalFields>\n\n {/* Actions */}\n <div className=\"flex items-center justify-end gap-3 border-t pt-6\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken transition-colors\"\n >\n <X size={16} />\n {tr(\"devportal.createApiKey.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting || apps.length === 0}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors disabled:opacity-50\"\n >\n <Save size={16} />\n {isSubmitting\n ? tr(\"devportal.createApiKey.generating\", \"Generating...\")\n : tr(\"devportal.createApiKey.submit\", \"Generate API Key\")}\n </button>\n </div>\n </form>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}