@burdenoff/microfe-devportal 2026.525.3 → 2026.528.1

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.
@@ -13,68 +13,69 @@ import { useState as m } from "react";
13
13
  import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
14
14
  import { Plus as g, Save as _, Trash2 as v, X as y } from "lucide-react";
15
15
  import { jsx as b, jsxs as x } from "react/jsx-runtime";
16
+ import { OptionalFields as S } from "@burdenoff/fe-libs/ui";
16
17
  //#region src/devportal/pages/CreateOAuthClientPage.tsx
17
- var S = () => {
18
- let { navigate: S, basePath: C } = e(), { t: w } = h(), T = (e, t) => {
19
- let n = w(e);
18
+ var C = () => {
19
+ let { navigate: C, basePath: w } = e(), { t: T } = h(), E = (e, t) => {
20
+ let n = T(e);
20
21
  return n === e ? t : n;
21
- }, [E, D] = m(""), [O, k] = m(""), [A, j] = m("CONFIDENTIAL"), [M, N] = m([]), [P, F] = m(""), [I, L] = m([]), [R, z] = m(""), [B, V] = m(null), [H, U] = m(null), [W, { loading: G, error: K }] = u(), q = (e) => {
22
- S?.(`${C}${e}`);
23
- }, J = () => {
24
- let e = P.trim();
25
- e && !M.includes(e) && (N([...M, e]), F(""));
26
- }, Y = (e) => {
27
- N(M.filter((t) => t !== e));
28
- }, X = () => {
29
- let e = R.trim();
30
- e && !I.includes(e) && (L([...I, e]), z(""));
31
- }, Z = (e) => {
32
- L(I.filter((t) => t !== e));
22
+ }, [D, O] = m(""), [k, A] = m(""), [j, M] = m("CONFIDENTIAL"), [N, P] = m([]), [F, I] = m(""), [L, R] = m([]), [z, B] = m(""), [V, H] = m(null), [U, W] = m(null), [G, { loading: K, error: q }] = u(), J = (e) => {
23
+ C?.(`${w}${e}`);
24
+ }, Y = () => {
25
+ let e = F.trim();
26
+ e && !N.includes(e) && (P([...N, e]), I(""));
27
+ }, X = (e) => {
28
+ P(N.filter((t) => t !== e));
29
+ }, Z = () => {
30
+ let e = z.trim();
31
+ e && !L.includes(e) && (R([...L, e]), B(""));
32
+ }, Q = (e) => {
33
+ R(L.filter((t) => t !== e));
33
34
  };
34
- return B ? /* @__PURE__ */ b(n, {
35
- title: T("devportal.appCreated", "App Created!"),
36
- description: T("devportal.appCreatedDescription", "Save the client secret below. It will not be shown again."),
35
+ return V ? /* @__PURE__ */ b(n, {
36
+ title: E("devportal.appCreated", "App Created!"),
37
+ description: E("devportal.appCreatedDescription", "Save the client secret below. It will not be shown again."),
37
38
  backLink: "/oauth",
38
- backLabel: T("devportal.backToApps", "Back to Apps"),
39
+ backLabel: E("devportal.backToApps", "Back to Apps"),
39
40
  children: /* @__PURE__ */ x("div", {
40
41
  className: "rounded-lg border border-border-subtle bg-status-success-bg-subtle p-6",
41
42
  children: [
42
43
  /* @__PURE__ */ b("h3", {
43
44
  className: "font-medium text-status-success-text",
44
- children: T("devportal.oauthAppCreated", "Your OAuth App has been created")
45
+ children: E("devportal.oauthAppCreated", "Your OAuth App has been created")
45
46
  }),
46
47
  /* @__PURE__ */ x("div", {
47
48
  className: "mt-4 space-y-3",
48
49
  children: [/* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("label", {
49
50
  className: "text-xs font-medium text-status-success-text",
50
- children: T("devportal.clientSecret", "Client Secret")
51
+ children: E("devportal.clientSecret", "Client Secret")
51
52
  }), /* @__PURE__ */ x("div", {
52
53
  className: "mt-1 flex items-center gap-2",
53
54
  children: [/* @__PURE__ */ b("code", {
54
- className: "flex-1 rounded border bg-white px-3 py-2 text-sm font-mono break-all",
55
- children: B
55
+ className: "flex-1 rounded border bg-bg-surface px-3 py-2 text-sm font-mono break-all",
56
+ children: V
56
57
  }), /* @__PURE__ */ b("button", {
57
58
  onClick: async () => {
58
- a("light"), o(await i(B) ? "success" : "error");
59
+ a("light"), o(await i(V) ? "success" : "error");
59
60
  },
60
61
  className: "rounded bg-status-success-bg-subtle px-3 py-2 text-sm font-medium text-status-success-text hover:bg-status-success-bg-subtle transition-colors",
61
- children: T("devportal.copy", "Copy")
62
+ children: E("devportal.copy", "Copy")
62
63
  })]
63
64
  })] }), /* @__PURE__ */ b("p", {
64
65
  className: "text-sm text-status-success-text",
65
- children: T("devportal.storeSecretSecurely", "Store this secret securely. You will not be able to see it again.")
66
+ children: E("devportal.storeSecretSecurely", "Store this secret securely. You will not be able to see it again.")
66
67
  })]
67
68
  }),
68
69
  /* @__PURE__ */ x("div", {
69
70
  className: "mt-6 flex gap-3",
70
- children: [H && /* @__PURE__ */ b("button", {
71
- onClick: () => q(`/oauth/${H}`),
71
+ children: [U && /* @__PURE__ */ b("button", {
72
+ onClick: () => J(`/oauth/${U}`),
72
73
  className: "rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors",
73
- children: T("devportal.viewAppDetails", "View App Details")
74
+ children: E("devportal.viewAppDetails", "View App Details")
74
75
  }), /* @__PURE__ */ b("button", {
75
- onClick: () => q("/oauth"),
76
+ onClick: () => J("/oauth"),
76
77
  className: "rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted transition-colors",
77
- children: T("devportal.backToApps", "Back to Apps")
78
+ children: E("devportal.backToApps", "Back to Apps")
78
79
  })]
79
80
  })
80
81
  ]
@@ -82,162 +83,165 @@ var S = () => {
82
83
  }) : /* @__PURE__ */ b(d, {
83
84
  permission: f.APP_CREATE,
84
85
  children: /* @__PURE__ */ b(n, {
85
- title: T("devportal.createOAuthApp", "Create OAuth App"),
86
- description: T("devportal.createOAuthAppDescription", "Register a new OAuth application for authentication"),
86
+ title: E("devportal.createOAuthApp", "Create OAuth App"),
87
+ description: E("devportal.createOAuthAppDescription", "Register a new OAuth application for authentication"),
87
88
  backLink: "/oauth",
88
- backLabel: T("devportal.backToApps", "Back to Apps"),
89
+ backLabel: E("devportal.backToApps", "Back to Apps"),
89
90
  children: /* @__PURE__ */ x("form", {
90
91
  onSubmit: async (e) => {
91
- if (e.preventDefault(), E.trim()) try {
92
- let e = await W({ variables: { input: {
93
- name: E.trim(),
94
- description: O.trim() || void 0,
95
- type: A,
96
- scopes: M,
97
- redirectUris: I
92
+ if (e.preventDefault(), D.trim()) try {
93
+ let e = await G({ variables: { input: {
94
+ name: D.trim(),
95
+ description: k.trim() || void 0,
96
+ type: j,
97
+ scopes: N,
98
+ redirectUris: L
98
99
  } } }), t = e.data?.createApp?.clientSecret, n = e.data?.createApp?.app?.id;
99
- t && (V(t), U(n || null));
100
+ t && (H(t), W(n || null));
100
101
  } catch {}
101
102
  },
102
103
  className: "space-y-6 max-w-2xl",
103
104
  children: [
104
- K && /* @__PURE__ */ b("div", {
105
+ q && /* @__PURE__ */ b("div", {
105
106
  className: "rounded-lg border border-destructive/50 bg-destructive/10 p-4",
106
107
  children: /* @__PURE__ */ b("p", {
107
108
  className: "text-sm text-destructive",
108
- children: p(K)
109
+ children: p(q)
109
110
  })
110
111
  }),
111
112
  /* @__PURE__ */ x(r, {
112
- title: T("devportal.basicInformation", "Basic Information"),
113
+ title: E("devportal.basicInformation", "Basic Information"),
113
114
  children: [
114
115
  /* @__PURE__ */ b(s, {
115
- label: T("devportal.appName", "App Name"),
116
- value: E,
117
- onChange: (e) => D(e.target.value),
118
- placeholder: T("devportal.myOAuthAppPlaceholder", "My OAuth App"),
116
+ label: E("devportal.appName", "App Name"),
117
+ value: D,
118
+ onChange: (e) => O(e.target.value),
119
+ placeholder: E("devportal.myOAuthAppPlaceholder", "My OAuth App"),
119
120
  required: !0
120
121
  }),
121
122
  /* @__PURE__ */ b(l, {
122
- label: T("devportal.description", "Description"),
123
- value: O,
124
- onChange: (e) => k(e.target.value),
125
- placeholder: T("devportal.appDescriptionPlaceholder", "Brief description of what this app does...")
123
+ label: E("devportal.description", "Description"),
124
+ value: k,
125
+ onChange: (e) => A(e.target.value),
126
+ placeholder: E("devportal.appDescriptionPlaceholder", "Brief description of what this app does...")
126
127
  }),
127
128
  /* @__PURE__ */ b(c, {
128
- label: T("devportal.appType", "App Type"),
129
- value: A,
130
- onChange: (e) => j(e.target.value),
129
+ label: E("devportal.appType", "App Type"),
130
+ value: j,
131
+ onChange: (e) => M(e.target.value),
131
132
  options: [
132
133
  {
133
134
  value: "SERVICE",
134
- label: T("devportal.appTypeService", "Service (M2M)")
135
+ label: E("devportal.appTypeService", "Service (M2M)")
135
136
  },
136
137
  {
137
138
  value: "INTEGRATION",
138
- label: T("devportal.appTypeIntegration", "Integration")
139
+ label: E("devportal.appTypeIntegration", "Integration")
139
140
  },
140
141
  {
141
142
  value: "BOT",
142
- label: T("devportal.appTypeBot", "Bot")
143
+ label: E("devportal.appTypeBot", "Bot")
143
144
  },
144
145
  {
145
146
  value: "WEB_APP",
146
- label: T("devportal.appTypeWebApp", "Web App")
147
+ label: E("devportal.appTypeWebApp", "Web App")
147
148
  },
148
149
  {
149
150
  value: "AUTOMATION",
150
- label: T("devportal.appTypeAutomation", "Automation")
151
+ label: E("devportal.appTypeAutomation", "Automation")
151
152
  }
152
153
  ]
153
154
  })
154
155
  ]
155
156
  }),
156
- /* @__PURE__ */ b(r, {
157
- title: T("devportal.scopes", "Scopes"),
158
- children: /* @__PURE__ */ x("div", {
159
- className: "space-y-3",
160
- children: [/* @__PURE__ */ x("div", {
161
- className: "flex items-center gap-2",
162
- children: [/* @__PURE__ */ b("input", {
163
- type: "text",
164
- value: P,
165
- onChange: (e) => F(e.target.value),
166
- onKeyDown: (e) => {
167
- e.key === "Enter" && (e.preventDefault(), J());
168
- },
169
- placeholder: "e.g. workspace:read",
170
- className: "flex-1 rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
171
- }), /* @__PURE__ */ b("button", {
172
- type: "button",
173
- onClick: J,
174
- className: "rounded-md bg-primary/10 p-2 text-primary hover:bg-primary/20 transition-colors",
175
- children: /* @__PURE__ */ b(g, { size: 16 })
176
- })]
177
- }), M.length > 0 && /* @__PURE__ */ b("div", {
178
- className: "flex flex-wrap gap-2",
179
- children: M.map((e) => /* @__PURE__ */ x("span", {
180
- className: "inline-flex items-center gap-1 rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary",
181
- children: [e, /* @__PURE__ */ b("button", {
157
+ /* @__PURE__ */ x(S, {
158
+ title: E("devportal.advancedOAuthSettings", "Advanced settings"),
159
+ description: E("devportal.advancedOAuthSettingsDescription", "Advanced grant types, PKCE-required toggles, scope tuning, and redirect URIs."),
160
+ children: [/* @__PURE__ */ b(r, {
161
+ title: E("devportal.scopes", "Scopes"),
162
+ children: /* @__PURE__ */ x("div", {
163
+ className: "space-y-3",
164
+ children: [/* @__PURE__ */ x("div", {
165
+ className: "flex items-center gap-2",
166
+ children: [/* @__PURE__ */ b("input", {
167
+ type: "text",
168
+ value: F,
169
+ onChange: (e) => I(e.target.value),
170
+ onKeyDown: (e) => {
171
+ e.key === "Enter" && (e.preventDefault(), Y());
172
+ },
173
+ placeholder: "e.g. workspace:read",
174
+ className: "flex-1 rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
175
+ }), /* @__PURE__ */ b("button", {
182
176
  type: "button",
183
- onClick: () => Y(e),
184
- className: "hover:text-destructive",
185
- children: /* @__PURE__ */ b(y, { size: 12 })
177
+ onClick: Y,
178
+ className: "rounded-md bg-primary/10 p-2 text-primary hover:bg-primary/20 transition-colors",
179
+ children: /* @__PURE__ */ b(g, { size: 16 })
186
180
  })]
187
- }, e))
188
- })]
189
- })
190
- }),
191
- /* @__PURE__ */ b(r, {
192
- title: T("devportal.redirectUris", "Redirect URIs"),
193
- children: /* @__PURE__ */ x("div", {
194
- className: "space-y-3",
195
- children: [/* @__PURE__ */ x("div", {
196
- className: "flex items-center gap-2",
197
- children: [/* @__PURE__ */ b("input", {
198
- type: "url",
199
- value: R,
200
- onChange: (e) => z(e.target.value),
201
- onKeyDown: (e) => {
202
- e.key === "Enter" && (e.preventDefault(), X());
203
- },
204
- placeholder: "https://example.com/callback",
205
- className: "flex-1 rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
206
- }), /* @__PURE__ */ b("button", {
207
- type: "button",
208
- onClick: X,
209
- className: "rounded-md bg-primary/10 p-2 text-primary hover:bg-primary/20 transition-colors",
210
- children: /* @__PURE__ */ b(g, { size: 16 })
181
+ }), N.length > 0 && /* @__PURE__ */ b("div", {
182
+ className: "flex flex-wrap gap-2",
183
+ children: N.map((e) => /* @__PURE__ */ x("span", {
184
+ className: "inline-flex items-center gap-1 rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary",
185
+ children: [e, /* @__PURE__ */ b("button", {
186
+ type: "button",
187
+ onClick: () => X(e),
188
+ className: "hover:text-destructive",
189
+ children: /* @__PURE__ */ b(y, { size: 12 })
190
+ })]
191
+ }, e))
211
192
  })]
212
- }), I.length > 0 && /* @__PURE__ */ b("div", {
213
- className: "space-y-2",
214
- children: I.map((e) => /* @__PURE__ */ x("div", {
193
+ })
194
+ }), /* @__PURE__ */ b(r, {
195
+ title: E("devportal.redirectUris", "Redirect URIs"),
196
+ children: /* @__PURE__ */ x("div", {
197
+ className: "space-y-3",
198
+ children: [/* @__PURE__ */ x("div", {
215
199
  className: "flex items-center gap-2",
216
- children: [/* @__PURE__ */ b("code", {
217
- className: "flex-1 rounded bg-muted px-3 py-1.5 text-sm font-mono",
218
- children: e
200
+ children: [/* @__PURE__ */ b("input", {
201
+ type: "url",
202
+ value: z,
203
+ onChange: (e) => B(e.target.value),
204
+ onKeyDown: (e) => {
205
+ e.key === "Enter" && (e.preventDefault(), Z());
206
+ },
207
+ placeholder: "https://example.com/callback",
208
+ className: "flex-1 rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
219
209
  }), /* @__PURE__ */ b("button", {
220
210
  type: "button",
221
- onClick: () => Z(e),
222
- className: "rounded p-1 text-muted-foreground hover:text-destructive transition-colors",
223
- children: /* @__PURE__ */ b(v, { size: 14 })
211
+ onClick: Z,
212
+ className: "rounded-md bg-primary/10 p-2 text-primary hover:bg-primary/20 transition-colors",
213
+ children: /* @__PURE__ */ b(g, { size: 16 })
224
214
  })]
225
- }, e))
226
- })]
227
- })
215
+ }), L.length > 0 && /* @__PURE__ */ b("div", {
216
+ className: "space-y-2",
217
+ children: L.map((e) => /* @__PURE__ */ x("div", {
218
+ className: "flex items-center gap-2",
219
+ children: [/* @__PURE__ */ b("code", {
220
+ className: "flex-1 rounded bg-muted px-3 py-1.5 text-sm font-mono",
221
+ children: e
222
+ }), /* @__PURE__ */ b("button", {
223
+ type: "button",
224
+ onClick: () => Q(e),
225
+ className: "rounded p-1 text-muted-foreground hover:text-destructive transition-colors",
226
+ children: /* @__PURE__ */ b(v, { size: 14 })
227
+ })]
228
+ }, e))
229
+ })]
230
+ })
231
+ })]
228
232
  }),
229
233
  /* @__PURE__ */ x("div", {
230
234
  className: "flex items-center gap-3 pt-4 border-t",
231
235
  children: [/* @__PURE__ */ x("button", {
232
236
  type: "submit",
233
- disabled: G || !E.trim(),
237
+ disabled: K || !D.trim(),
234
238
  className: t("inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-colors", "bg-primary text-primary-foreground hover:bg-primary/90", "disabled:opacity-50 disabled:cursor-not-allowed"),
235
- children: [/* @__PURE__ */ b(_, { size: 16 }), G ? T("devportal.creating", "Creating...") : T("devportal.createApp", "Create App")]
239
+ children: [/* @__PURE__ */ b(_, { size: 16 }), K ? E("devportal.creating", "Creating...") : E("devportal.createApp", "Create App")]
236
240
  }), /* @__PURE__ */ x("button", {
237
241
  type: "button",
238
- onClick: () => q("/oauth"),
242
+ onClick: () => J("/oauth"),
239
243
  className: "inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted transition-colors",
240
- children: [/* @__PURE__ */ b(y, { size: 16 }), T("devportal.cancel", "Cancel")]
244
+ children: [/* @__PURE__ */ b(y, { size: 16 }), E("devportal.cancel", "Cancel")]
241
245
  })]
242
246
  })
243
247
  ]
@@ -246,6 +250,6 @@ var S = () => {
246
250
  });
247
251
  };
248
252
  //#endregion
249
- export { S as CreateOAuthClientPage };
253
+ export { C as CreateOAuthClientPage };
250
254
 
251
255
  //# sourceMappingURL=CreateOAuthClientPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CreateOAuthClientPage.js","names":[],"sources":["../../../src/devportal/pages/CreateOAuthClientPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport { Save, X, Plus, Trash2 } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n SelectField,\n TextareaField,\n} from \"../components/FormField\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { cn } from \"../utils/cn\";\nimport {\n nativeCopyText,\n nativeImpact,\n nativeNotify,\n} from \"../../utils/nativeBridge\";\nimport { useCreateAuthAppMutation, getErrorMessage } from \"../hooks\";\n\n/**\n * Create OAuth App page\n *\n * Creates a new App in auth-svc (replaces devportal-svc CreateOAuthClient).\n * Shows the client secret once after creation.\n */\nexport const CreateOAuthClientPage: FC = () => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const [name, setName] = useState(\"\");\n const [description, setDescription] = useState(\"\");\n const [type, setType] = useState<string>(\"CONFIDENTIAL\");\n const [scopes, setScopes] = useState<string[]>([]);\n const [newScope, setNewScope] = useState(\"\");\n const [redirectUris, setRedirectUris] = useState<string[]>([]);\n const [newUri, setNewUri] = useState(\"\");\n\n const [createdSecret, setCreatedSecret] = useState<string | null>(null);\n const [createdAppId, setCreatedAppId] = useState<string | null>(null);\n\n const [createApp, { loading, error }] = useCreateAuthAppMutation();\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const handleAddScope = () => {\n const trimmed = newScope.trim();\n if (trimmed && !scopes.includes(trimmed)) {\n setScopes([...scopes, trimmed]);\n setNewScope(\"\");\n }\n };\n\n const handleRemoveScope = (scope: string) => {\n setScopes(scopes.filter((s) => s !== scope));\n };\n\n const handleAddUri = () => {\n const trimmed = newUri.trim();\n if (trimmed && !redirectUris.includes(trimmed)) {\n setRedirectUris([...redirectUris, trimmed]);\n setNewUri(\"\");\n }\n };\n\n const handleRemoveUri = (uri: string) => {\n setRedirectUris(redirectUris.filter((u) => u !== uri));\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!name.trim()) return;\n\n try {\n const result = await createApp({\n variables: {\n input: {\n name: name.trim(),\n description: description.trim() || undefined,\n type,\n scopes,\n redirectUris,\n },\n },\n });\n\n const secret = result.data?.createApp?.clientSecret;\n const appId = result.data?.createApp?.app?.id;\n if (secret) {\n setCreatedSecret(secret);\n setCreatedAppId(appId || null);\n }\n } catch {\n // error handled by Apollo\n }\n };\n\n // Success — show the secret\n if (createdSecret) {\n return (\n <PageLayout\n title={tr(\"devportal.appCreated\", \"App Created!\")}\n description={tr(\n \"devportal.appCreatedDescription\",\n \"Save the client secret below. It will not be shown again.\",\n )}\n backLink=\"/oauth\"\n backLabel={tr(\"devportal.backToApps\", \"Back to Apps\")}\n >\n <div className=\"rounded-lg border border-border-subtle bg-status-success-bg-subtle p-6\">\n <h3 className=\"font-medium text-status-success-text\">\n {tr(\"devportal.oauthAppCreated\", \"Your OAuth App has been created\")}\n </h3>\n <div className=\"mt-4 space-y-3\">\n <div>\n <label className=\"text-xs font-medium text-status-success-text\">\n {tr(\"devportal.clientSecret\", \"Client Secret\")}\n </label>\n <div className=\"mt-1 flex items-center gap-2\">\n <code className=\"flex-1 rounded border bg-white px-3 py-2 text-sm font-mono break-all\">\n {createdSecret}\n </code>\n <button\n onClick={async () => {\n void nativeImpact(\"light\");\n const ok = await nativeCopyText(createdSecret);\n void nativeNotify(ok ? \"success\" : \"error\");\n }}\n className=\"rounded bg-status-success-bg-subtle px-3 py-2 text-sm font-medium text-status-success-text hover:bg-status-success-bg-subtle transition-colors\"\n >\n {tr(\"devportal.copy\", \"Copy\")}\n </button>\n </div>\n </div>\n <p className=\"text-sm text-status-success-text\">\n {tr(\n \"devportal.storeSecretSecurely\",\n \"Store this secret securely. You will not be able to see it again.\",\n )}\n </p>\n </div>\n <div className=\"mt-6 flex gap-3\">\n {createdAppId && (\n <button\n onClick={() => handleNavigate(`/oauth/${createdAppId}`)}\n className=\"rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n >\n {tr(\"devportal.viewAppDetails\", \"View App Details\")}\n </button>\n )}\n <button\n onClick={() => handleNavigate(\"/oauth\")}\n className=\"rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted transition-colors\"\n >\n {tr(\"devportal.backToApps\", \"Back to Apps\")}\n </button>\n </div>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_CREATE}>\n <PageLayout\n title={tr(\"devportal.createOAuthApp\", \"Create OAuth App\")}\n description={tr(\n \"devportal.createOAuthAppDescription\",\n \"Register a new OAuth application for authentication\",\n )}\n backLink=\"/oauth\"\n backLabel={tr(\"devportal.backToApps\", \"Back to Apps\")}\n >\n <form onSubmit={handleSubmit} className=\"space-y-6 max-w-2xl\">\n {error && (\n <div className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-4\">\n <p className=\"text-sm text-destructive\">\n {getErrorMessage(error)}\n </p>\n </div>\n )}\n\n <PageSection\n title={tr(\"devportal.basicInformation\", \"Basic Information\")}\n >\n <InputField\n label={tr(\"devportal.appName\", \"App Name\")}\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder={tr(\n \"devportal.myOAuthAppPlaceholder\",\n \"My OAuth App\",\n )}\n required\n />\n <TextareaField\n label={tr(\"devportal.description\", \"Description\")}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder={tr(\n \"devportal.appDescriptionPlaceholder\",\n \"Brief description of what this app does...\",\n )}\n />\n <SelectField\n label={tr(\"devportal.appType\", \"App Type\")}\n value={type}\n onChange={(e) => setType(e.target.value)}\n options={[\n {\n value: \"SERVICE\",\n label: tr(\"devportal.appTypeService\", \"Service (M2M)\"),\n },\n {\n value: \"INTEGRATION\",\n label: tr(\"devportal.appTypeIntegration\", \"Integration\"),\n },\n { value: \"BOT\", label: tr(\"devportal.appTypeBot\", \"Bot\") },\n {\n value: \"WEB_APP\",\n label: tr(\"devportal.appTypeWebApp\", \"Web App\"),\n },\n {\n value: \"AUTOMATION\",\n label: tr(\"devportal.appTypeAutomation\", \"Automation\"),\n },\n ]}\n />\n </PageSection>\n\n <PageSection title={tr(\"devportal.scopes\", \"Scopes\")}>\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <input\n type=\"text\"\n value={newScope}\n onChange={(e) => setNewScope(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n handleAddScope();\n }\n }}\n placeholder=\"e.g. workspace:read\"\n className=\"flex-1 rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n <button\n type=\"button\"\n onClick={handleAddScope}\n className=\"rounded-md bg-primary/10 p-2 text-primary hover:bg-primary/20 transition-colors\"\n >\n <Plus size={16} />\n </button>\n </div>\n {scopes.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {scopes.map((scope) => (\n <span\n key={scope}\n className=\"inline-flex items-center gap-1 rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary\"\n >\n {scope}\n <button\n type=\"button\"\n onClick={() => handleRemoveScope(scope)}\n className=\"hover:text-destructive\"\n >\n <X size={12} />\n </button>\n </span>\n ))}\n </div>\n )}\n </div>\n </PageSection>\n\n <PageSection title={tr(\"devportal.redirectUris\", \"Redirect URIs\")}>\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <input\n type=\"url\"\n value={newUri}\n onChange={(e) => setNewUri(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n handleAddUri();\n }\n }}\n placeholder=\"https://example.com/callback\"\n className=\"flex-1 rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n <button\n type=\"button\"\n onClick={handleAddUri}\n className=\"rounded-md bg-primary/10 p-2 text-primary hover:bg-primary/20 transition-colors\"\n >\n <Plus size={16} />\n </button>\n </div>\n {redirectUris.length > 0 && (\n <div className=\"space-y-2\">\n {redirectUris.map((uri) => (\n <div key={uri} className=\"flex items-center gap-2\">\n <code className=\"flex-1 rounded bg-muted px-3 py-1.5 text-sm font-mono\">\n {uri}\n </code>\n <button\n type=\"button\"\n onClick={() => handleRemoveUri(uri)}\n className=\"rounded p-1 text-muted-foreground hover:text-destructive transition-colors\"\n >\n <Trash2 size={14} />\n </button>\n </div>\n ))}\n </div>\n )}\n </div>\n </PageSection>\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-4 border-t\">\n <button\n type=\"submit\"\n disabled={loading || !name.trim()}\n className={cn(\n \"inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-colors\",\n \"bg-primary text-primary-foreground hover:bg-primary/90\",\n \"disabled:opacity-50 disabled:cursor-not-allowed\",\n )}\n >\n <Save size={16} />\n {loading\n ? tr(\"devportal.creating\", \"Creating...\")\n : tr(\"devportal.createApp\", \"Create App\")}\n </button>\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/oauth\")}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted transition-colors\"\n >\n <X size={16} />\n {tr(\"devportal.cancel\", \"Cancel\")}\n </button>\n </div>\n </form>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"CreateOAuthClientPage.js","names":[],"sources":["../../../src/devportal/pages/CreateOAuthClientPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport { Save, X, Plus, Trash2 } 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 {\n InputField,\n SelectField,\n TextareaField,\n} from \"../components/FormField\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { cn } from \"../utils/cn\";\nimport {\n nativeCopyText,\n nativeImpact,\n nativeNotify,\n} from \"../../utils/nativeBridge\";\nimport { useCreateAuthAppMutation, getErrorMessage } from \"../hooks\";\n\n/**\n * Create OAuth App page\n *\n * Creates a new App in auth-svc (replaces devportal-svc CreateOAuthClient).\n * Shows the client secret once after creation.\n */\nexport const CreateOAuthClientPage: FC = () => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const [name, setName] = useState(\"\");\n const [description, setDescription] = useState(\"\");\n const [type, setType] = useState<string>(\"CONFIDENTIAL\");\n const [scopes, setScopes] = useState<string[]>([]);\n const [newScope, setNewScope] = useState(\"\");\n const [redirectUris, setRedirectUris] = useState<string[]>([]);\n const [newUri, setNewUri] = useState(\"\");\n\n const [createdSecret, setCreatedSecret] = useState<string | null>(null);\n const [createdAppId, setCreatedAppId] = useState<string | null>(null);\n\n const [createApp, { loading, error }] = useCreateAuthAppMutation();\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const handleAddScope = () => {\n const trimmed = newScope.trim();\n if (trimmed && !scopes.includes(trimmed)) {\n setScopes([...scopes, trimmed]);\n setNewScope(\"\");\n }\n };\n\n const handleRemoveScope = (scope: string) => {\n setScopes(scopes.filter((s) => s !== scope));\n };\n\n const handleAddUri = () => {\n const trimmed = newUri.trim();\n if (trimmed && !redirectUris.includes(trimmed)) {\n setRedirectUris([...redirectUris, trimmed]);\n setNewUri(\"\");\n }\n };\n\n const handleRemoveUri = (uri: string) => {\n setRedirectUris(redirectUris.filter((u) => u !== uri));\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!name.trim()) return;\n\n try {\n const result = await createApp({\n variables: {\n input: {\n name: name.trim(),\n description: description.trim() || undefined,\n type,\n scopes,\n redirectUris,\n },\n },\n });\n\n const secret = result.data?.createApp?.clientSecret;\n const appId = result.data?.createApp?.app?.id;\n if (secret) {\n setCreatedSecret(secret);\n setCreatedAppId(appId || null);\n }\n } catch {\n // error handled by Apollo\n }\n };\n\n // Success — show the secret\n if (createdSecret) {\n return (\n <PageLayout\n title={tr(\"devportal.appCreated\", \"App Created!\")}\n description={tr(\n \"devportal.appCreatedDescription\",\n \"Save the client secret below. It will not be shown again.\",\n )}\n backLink=\"/oauth\"\n backLabel={tr(\"devportal.backToApps\", \"Back to Apps\")}\n >\n <div className=\"rounded-lg border border-border-subtle bg-status-success-bg-subtle p-6\">\n <h3 className=\"font-medium text-status-success-text\">\n {tr(\"devportal.oauthAppCreated\", \"Your OAuth App has been created\")}\n </h3>\n <div className=\"mt-4 space-y-3\">\n <div>\n <label className=\"text-xs font-medium text-status-success-text\">\n {tr(\"devportal.clientSecret\", \"Client Secret\")}\n </label>\n <div className=\"mt-1 flex items-center gap-2\">\n <code className=\"flex-1 rounded border bg-bg-surface px-3 py-2 text-sm font-mono break-all\">\n {createdSecret}\n </code>\n <button\n onClick={async () => {\n void nativeImpact(\"light\");\n const ok = await nativeCopyText(createdSecret);\n void nativeNotify(ok ? \"success\" : \"error\");\n }}\n className=\"rounded bg-status-success-bg-subtle px-3 py-2 text-sm font-medium text-status-success-text hover:bg-status-success-bg-subtle transition-colors\"\n >\n {tr(\"devportal.copy\", \"Copy\")}\n </button>\n </div>\n </div>\n <p className=\"text-sm text-status-success-text\">\n {tr(\n \"devportal.storeSecretSecurely\",\n \"Store this secret securely. You will not be able to see it again.\",\n )}\n </p>\n </div>\n <div className=\"mt-6 flex gap-3\">\n {createdAppId && (\n <button\n onClick={() => handleNavigate(`/oauth/${createdAppId}`)}\n className=\"rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n >\n {tr(\"devportal.viewAppDetails\", \"View App Details\")}\n </button>\n )}\n <button\n onClick={() => handleNavigate(\"/oauth\")}\n className=\"rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted transition-colors\"\n >\n {tr(\"devportal.backToApps\", \"Back to Apps\")}\n </button>\n </div>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_CREATE}>\n <PageLayout\n title={tr(\"devportal.createOAuthApp\", \"Create OAuth App\")}\n description={tr(\n \"devportal.createOAuthAppDescription\",\n \"Register a new OAuth application for authentication\",\n )}\n backLink=\"/oauth\"\n backLabel={tr(\"devportal.backToApps\", \"Back to Apps\")}\n >\n <form onSubmit={handleSubmit} className=\"space-y-6 max-w-2xl\">\n {error && (\n <div className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-4\">\n <p className=\"text-sm text-destructive\">\n {getErrorMessage(error)}\n </p>\n </div>\n )}\n\n <PageSection\n title={tr(\"devportal.basicInformation\", \"Basic Information\")}\n >\n <InputField\n label={tr(\"devportal.appName\", \"App Name\")}\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder={tr(\n \"devportal.myOAuthAppPlaceholder\",\n \"My OAuth App\",\n )}\n required\n />\n <TextareaField\n label={tr(\"devportal.description\", \"Description\")}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder={tr(\n \"devportal.appDescriptionPlaceholder\",\n \"Brief description of what this app does...\",\n )}\n />\n <SelectField\n label={tr(\"devportal.appType\", \"App Type\")}\n value={type}\n onChange={(e) => setType(e.target.value)}\n options={[\n {\n value: \"SERVICE\",\n label: tr(\"devportal.appTypeService\", \"Service (M2M)\"),\n },\n {\n value: \"INTEGRATION\",\n label: tr(\"devportal.appTypeIntegration\", \"Integration\"),\n },\n { value: \"BOT\", label: tr(\"devportal.appTypeBot\", \"Bot\") },\n {\n value: \"WEB_APP\",\n label: tr(\"devportal.appTypeWebApp\", \"Web App\"),\n },\n {\n value: \"AUTOMATION\",\n label: tr(\"devportal.appTypeAutomation\", \"Automation\"),\n },\n ]}\n />\n </PageSection>\n\n {/* Advanced: grant types / PKCE-required toggles / scope tuning / redirect URIs — collapse the long-tail knobs */}\n <OptionalFields\n title={tr(\"devportal.advancedOAuthSettings\", \"Advanced settings\")}\n description={tr(\n \"devportal.advancedOAuthSettingsDescription\",\n \"Advanced grant types, PKCE-required toggles, scope tuning, and redirect URIs.\",\n )}\n >\n <PageSection title={tr(\"devportal.scopes\", \"Scopes\")}>\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <input\n type=\"text\"\n value={newScope}\n onChange={(e) => setNewScope(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n handleAddScope();\n }\n }}\n placeholder=\"e.g. workspace:read\"\n className=\"flex-1 rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n <button\n type=\"button\"\n onClick={handleAddScope}\n className=\"rounded-md bg-primary/10 p-2 text-primary hover:bg-primary/20 transition-colors\"\n >\n <Plus size={16} />\n </button>\n </div>\n {scopes.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {scopes.map((scope) => (\n <span\n key={scope}\n className=\"inline-flex items-center gap-1 rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary\"\n >\n {scope}\n <button\n type=\"button\"\n onClick={() => handleRemoveScope(scope)}\n className=\"hover:text-destructive\"\n >\n <X size={12} />\n </button>\n </span>\n ))}\n </div>\n )}\n </div>\n </PageSection>\n\n <PageSection title={tr(\"devportal.redirectUris\", \"Redirect URIs\")}>\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <input\n type=\"url\"\n value={newUri}\n onChange={(e) => setNewUri(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n handleAddUri();\n }\n }}\n placeholder=\"https://example.com/callback\"\n className=\"flex-1 rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n <button\n type=\"button\"\n onClick={handleAddUri}\n className=\"rounded-md bg-primary/10 p-2 text-primary hover:bg-primary/20 transition-colors\"\n >\n <Plus size={16} />\n </button>\n </div>\n {redirectUris.length > 0 && (\n <div className=\"space-y-2\">\n {redirectUris.map((uri) => (\n <div key={uri} className=\"flex items-center gap-2\">\n <code className=\"flex-1 rounded bg-muted px-3 py-1.5 text-sm font-mono\">\n {uri}\n </code>\n <button\n type=\"button\"\n onClick={() => handleRemoveUri(uri)}\n className=\"rounded p-1 text-muted-foreground hover:text-destructive transition-colors\"\n >\n <Trash2 size={14} />\n </button>\n </div>\n ))}\n </div>\n )}\n </div>\n </PageSection>\n </OptionalFields>\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-4 border-t\">\n <button\n type=\"submit\"\n disabled={loading || !name.trim()}\n className={cn(\n \"inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-colors\",\n \"bg-primary text-primary-foreground hover:bg-primary/90\",\n \"disabled:opacity-50 disabled:cursor-not-allowed\",\n )}\n >\n <Save size={16} />\n {loading\n ? tr(\"devportal.creating\", \"Creating...\")\n : tr(\"devportal.createApp\", \"Create App\")}\n </button>\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/oauth\")}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted transition-colors\"\n >\n <X size={16} />\n {tr(\"devportal.cancel\", \"Cancel\")}\n </button>\n </div>\n </form>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
@@ -331,7 +331,7 @@ var v = [
331
331
  className: `h-4 w-4 rounded-full border-2 ${n ? "border-primary bg-primary" : "border-muted-foreground"}`,
332
332
  children: n && /* @__PURE__ */ g("div", {
333
333
  className: "h-full w-full flex items-center justify-center",
334
- children: /* @__PURE__ */ g("div", { className: "h-1.5 w-1.5 rounded-full bg-white" })
334
+ children: /* @__PURE__ */ g("div", { className: "h-1.5 w-1.5 rounded-full bg-bg-surface" })
335
335
  })
336
336
  })]
337
337
  }), /* @__PURE__ */ _("div", {