@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.
- package/dist/devportal/DevportalRoot.js +65 -119
- package/dist/devportal/DevportalRoot.js.map +1 -1
- package/dist/devportal/components/AppCard.js +4 -4
- package/dist/devportal/components/DeveloperSettingsHub.d.ts +6 -0
- package/dist/devportal/components/DeveloperSettingsHub.js +59 -0
- package/dist/devportal/components/DeveloperSettingsHub.js.map +1 -0
- package/dist/devportal/components/SectionNav.d.ts +1 -1
- package/dist/devportal/components/SectionNav.js +60 -36
- package/dist/devportal/components/SectionNav.js.map +1 -1
- package/dist/devportal/pages/AnalyticsDashboardPage.js +186 -179
- package/dist/devportal/pages/AnalyticsDashboardPage.js.map +1 -1
- package/dist/devportal/pages/ApiKeysPage.js +113 -111
- package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
- package/dist/devportal/pages/AppLogsPage.js +80 -84
- package/dist/devportal/pages/AppLogsPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationDetailPage.js +529 -504
- package/dist/devportal/pages/ApplicationDetailPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationsPage.js +68 -67
- package/dist/devportal/pages/ApplicationsPage.js.map +1 -1
- package/dist/devportal/pages/CreateApiKeyPage.js +163 -166
- package/dist/devportal/pages/CreateApiKeyPage.js.map +1 -1
- package/dist/devportal/pages/CreateOAuthClientPage.js +134 -130
- package/dist/devportal/pages/CreateOAuthClientPage.js.map +1 -1
- package/dist/devportal/pages/DelegationsPage.js +11 -11
- package/dist/devportal/pages/DocumentationPage.js +100 -94
- package/dist/devportal/pages/DocumentationPage.js.map +1 -1
- package/dist/devportal/pages/EditApplicationPage.js +379 -377
- package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
- package/dist/devportal/pages/InvitationDecisionPage.js +83 -79
- package/dist/devportal/pages/InvitationDecisionPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientDetailPage.js +76 -76
- package/dist/devportal/pages/OAuthClientDetailPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientsPage.js +126 -124
- package/dist/devportal/pages/OAuthClientsPage.js.map +1 -1
- package/dist/devportal/pages/OrganizationDetailPage.js +19 -19
- package/dist/devportal/pages/OrganizationMembersPage.js +223 -204
- package/dist/devportal/pages/OrganizationMembersPage.js.map +1 -1
- package/dist/devportal/pages/ResourcesPage.d.ts +6 -0
- package/dist/devportal/pages/ResourcesPage.js +171 -155
- package/dist/devportal/pages/ResourcesPage.js.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.js +13 -13
- package/dist/devportal/pages/SellerItemsPage.js +4 -4
- package/dist/devportal/pages/SettingsPage.js +330 -315
- package/dist/devportal/pages/SettingsPage.js.map +1 -1
- package/dist/devportal/pages/SetupPage.js +5 -5
- package/dist/devportal/pages/WebhookDetailPage.js +6 -6
- package/dist/devportal/pages/WebhooksPage.js +39 -39
- package/dist/devportal/pages/WebhooksPage.js.map +1 -1
- package/dist/devportal/utils/appDetailTabs.d.ts +73 -0
- package/dist/devportal/utils/appDetailTabs.js +62 -0
- package/dist/devportal/utils/appDetailTabs.js.map +1 -0
- package/dist/devportal/utils/developerNav.d.ts +39 -0
- package/dist/devportal/utils/developerNav.js +304 -0
- package/dist/devportal/utils/developerNav.js.map +1 -0
- package/package.json +2 -2
|
@@ -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 {
|
|
4
|
-
import {
|
|
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 {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
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 {
|
|
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
|
-
})) ?? [], [
|
|
35
|
+
})) ?? [], [L, R] = m({
|
|
39
36
|
appId: "",
|
|
40
37
|
name: "",
|
|
41
38
|
scopes: [],
|
|
42
39
|
expiresIn: "1year"
|
|
43
|
-
}),
|
|
44
|
-
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
},
|
|
48
|
-
|
|
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
|
-
})),
|
|
48
|
+
})), D[e] && O((t) => ({
|
|
52
49
|
...t,
|
|
53
50
|
[e]: ""
|
|
54
|
-
})),
|
|
55
|
-
},
|
|
51
|
+
})), k && A(null);
|
|
52
|
+
}, H = () => {
|
|
56
53
|
let e = {};
|
|
57
|
-
return
|
|
58
|
-
},
|
|
59
|
-
if (e.preventDefault(),
|
|
60
|
-
|
|
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(
|
|
63
|
-
appId:
|
|
64
|
-
name:
|
|
65
|
-
scope:
|
|
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
|
|
69
|
-
r?.generateAPIKey && (
|
|
65
|
+
}, { data: r } = await j({ variables: { input: n } });
|
|
66
|
+
r?.generateAPIKey && (N.emit("devportal.apikey.generated", {
|
|
70
67
|
keyId: r.generateAPIKey.keyId,
|
|
71
|
-
appId:
|
|
72
|
-
}),
|
|
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 :
|
|
79
|
-
|
|
75
|
+
let t = e instanceof Error ? e.message : x("devportal.failedToGenerateApiKey", "Failed to generate API key");
|
|
76
|
+
A(t);
|
|
80
77
|
}
|
|
81
78
|
}
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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__ */
|
|
92
|
-
title: "API Key & Secret",
|
|
93
|
-
secret:
|
|
94
|
-
description: "Store this secret securely — you won't be able to see it again.",
|
|
95
|
-
onDismiss:
|
|
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__ */
|
|
119
|
+
/* @__PURE__ */ v(c, {
|
|
98
120
|
variant: "info",
|
|
99
|
-
title: "Usage Instructions",
|
|
100
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
104
|
-
/* @__PURE__ */
|
|
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__ */
|
|
128
|
+
children: [/* @__PURE__ */ y("div", {
|
|
107
129
|
className: "flex items-center gap-2",
|
|
108
|
-
children: [/* @__PURE__ */
|
|
130
|
+
children: [/* @__PURE__ */ v("span", {
|
|
109
131
|
className: "text-xs font-medium text-text-secondary",
|
|
110
|
-
children: "Key ID:"
|
|
111
|
-
}), /* @__PURE__ */
|
|
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:
|
|
135
|
+
children: T.keyId
|
|
114
136
|
})]
|
|
115
|
-
}), /* @__PURE__ */
|
|
137
|
+
}), /* @__PURE__ */ y("div", {
|
|
116
138
|
className: "flex items-center gap-2",
|
|
117
|
-
children: [/* @__PURE__ */
|
|
139
|
+
children: [/* @__PURE__ */ v("span", {
|
|
118
140
|
className: "text-xs font-medium text-text-secondary",
|
|
119
|
-
children: "API Key:"
|
|
120
|
-
}), /* @__PURE__ */
|
|
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:
|
|
144
|
+
children: T.apiKey
|
|
123
145
|
})]
|
|
124
146
|
})]
|
|
125
147
|
}),
|
|
126
|
-
/* @__PURE__ */
|
|
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__ */
|
|
155
|
+
/* @__PURE__ */ v("div", {
|
|
134
156
|
className: "flex gap-3",
|
|
135
|
-
children: /* @__PURE__ */
|
|
136
|
-
onClick:
|
|
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__ */
|
|
165
|
+
}) : /* @__PURE__ */ v(u, {
|
|
144
166
|
authOnly: !0,
|
|
145
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
152
|
-
onSubmit:
|
|
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
|
-
|
|
177
|
+
k && /* @__PURE__ */ v(c, {
|
|
156
178
|
variant: "error",
|
|
157
|
-
title: "Error Generating API Key",
|
|
158
|
-
onDismiss: () =>
|
|
159
|
-
children:
|
|
179
|
+
title: x("devportal.createApiKey.errorTitle", "Error Generating API Key"),
|
|
180
|
+
onDismiss: () => A(null),
|
|
181
|
+
children: k
|
|
160
182
|
}),
|
|
161
|
-
/* @__PURE__ */
|
|
162
|
-
title: "Basic Information",
|
|
163
|
-
description: "Configure the API key settings",
|
|
164
|
-
children: /* @__PURE__ */
|
|
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: [
|
|
188
|
+
children: [F ? /* @__PURE__ */ y("div", {
|
|
167
189
|
className: "flex items-center gap-2 py-2",
|
|
168
|
-
children: [/* @__PURE__ */
|
|
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
|
-
}) :
|
|
194
|
+
}) : I.length === 0 ? /* @__PURE__ */ v(c, {
|
|
173
195
|
variant: "warning",
|
|
174
|
-
title: "No Apps Found",
|
|
175
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
200
|
+
/* @__PURE__ */ v("button", {
|
|
179
201
|
type: "button",
|
|
180
|
-
onClick: () =>
|
|
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__ */
|
|
186
|
-
label: "App",
|
|
187
|
-
value:
|
|
188
|
-
onChange: (e) =>
|
|
189
|
-
options:
|
|
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:
|
|
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__ */
|
|
198
|
-
label: "Key Name",
|
|
199
|
-
placeholder: "e.g., Production Backend Key",
|
|
200
|
-
value:
|
|
201
|
-
onChange: (e) =>
|
|
202
|
-
error:
|
|
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__ */
|
|
209
|
-
title: "Scopes",
|
|
210
|
-
description: "Define what this API key can access",
|
|
211
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
214
|
-
label: "Scopes",
|
|
215
|
-
value:
|
|
216
|
-
onChange: (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:
|
|
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__ */
|
|
224
|
-
title: "Advanced settings",
|
|
225
|
-
description: "Rate-limit overrides, allowed origins, scope tuning, and expiration.",
|
|
226
|
-
children: /* @__PURE__ */
|
|
227
|
-
title: "Key Expiration",
|
|
228
|
-
description: "Set when this key should expire",
|
|
229
|
-
children: [/* @__PURE__ */
|
|
230
|
-
label: "Expires In",
|
|
231
|
-
value:
|
|
232
|
-
onChange: (e) =>
|
|
233
|
-
options:
|
|
234
|
-
|
|
235
|
-
|
|
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__ */
|
|
264
|
+
/* @__PURE__ */ y("div", {
|
|
268
265
|
className: "flex items-center justify-end gap-3 border-t pt-6",
|
|
269
|
-
children: [/* @__PURE__ */
|
|
266
|
+
children: [/* @__PURE__ */ y("button", {
|
|
270
267
|
type: "button",
|
|
271
|
-
onClick:
|
|
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__ */ _
|
|
274
|
-
}), /* @__PURE__ */
|
|
270
|
+
children: [/* @__PURE__ */ v(_, { size: 16 }), x("devportal.createApiKey.cancel", "Cancel")]
|
|
271
|
+
}), /* @__PURE__ */ y("button", {
|
|
275
272
|
type: "submit",
|
|
276
|
-
disabled:
|
|
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__ */
|
|
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":"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"}
|
|
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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"}
|