@stigmer/react 0.0.84 → 0.0.86

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 (87) hide show
  1. package/demo/fixtures.d.ts +4 -0
  2. package/demo/fixtures.d.ts.map +1 -1
  3. package/demo/fixtures.js +4 -0
  4. package/demo/fixtures.js.map +1 -1
  5. package/demo/samples.d.ts +1 -0
  6. package/demo/samples.d.ts.map +1 -1
  7. package/demo/samples.js +1 -0
  8. package/demo/samples.js.map +1 -1
  9. package/execution/ArtifactPreviewModal.d.ts +78 -18
  10. package/execution/ArtifactPreviewModal.d.ts.map +1 -1
  11. package/execution/ArtifactPreviewModal.js +82 -60
  12. package/execution/ArtifactPreviewModal.js.map +1 -1
  13. package/execution/index.d.ts +2 -2
  14. package/execution/index.d.ts.map +1 -1
  15. package/execution/index.js +1 -1
  16. package/execution/index.js.map +1 -1
  17. package/index.d.ts +6 -4
  18. package/index.d.ts.map +1 -1
  19. package/index.js +4 -2
  20. package/index.js.map +1 -1
  21. package/library/ResourceListView.js +1 -1
  22. package/library/ResourceListView.js.map +1 -1
  23. package/mcp-server/McpServerConnectDialog.d.ts +51 -0
  24. package/mcp-server/McpServerConnectDialog.d.ts.map +1 -0
  25. package/mcp-server/McpServerConnectDialog.js +164 -0
  26. package/mcp-server/McpServerConnectDialog.js.map +1 -0
  27. package/mcp-server/McpServerDetailView.js +2 -2
  28. package/mcp-server/McpServerDetailView.js.map +1 -1
  29. package/mcp-server/McpServerPicker.d.ts.map +1 -1
  30. package/mcp-server/McpServerPicker.js +7 -1
  31. package/mcp-server/McpServerPicker.js.map +1 -1
  32. package/mcp-server/index.d.ts +2 -0
  33. package/mcp-server/index.d.ts.map +1 -1
  34. package/mcp-server/index.js +1 -0
  35. package/mcp-server/index.js.map +1 -1
  36. package/oauth-app/CreateOAuthAppForm.d.ts +41 -0
  37. package/oauth-app/CreateOAuthAppForm.d.ts.map +1 -0
  38. package/oauth-app/CreateOAuthAppForm.js +140 -0
  39. package/oauth-app/CreateOAuthAppForm.js.map +1 -0
  40. package/oauth-app/OAuthAppDetailPanel.d.ts +43 -0
  41. package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -0
  42. package/oauth-app/OAuthAppDetailPanel.js +202 -0
  43. package/oauth-app/OAuthAppDetailPanel.js.map +1 -0
  44. package/oauth-app/OAuthAppListPanel.d.ts +43 -0
  45. package/oauth-app/OAuthAppListPanel.d.ts.map +1 -0
  46. package/oauth-app/OAuthAppListPanel.js +79 -0
  47. package/oauth-app/OAuthAppListPanel.js.map +1 -0
  48. package/oauth-app/index.d.ts +15 -0
  49. package/oauth-app/index.d.ts.map +1 -0
  50. package/oauth-app/index.js +8 -0
  51. package/oauth-app/index.js.map +1 -0
  52. package/oauth-app/useCreateOAuthApp.d.ts +39 -0
  53. package/oauth-app/useCreateOAuthApp.d.ts.map +1 -0
  54. package/oauth-app/useCreateOAuthApp.js +50 -0
  55. package/oauth-app/useCreateOAuthApp.js.map +1 -0
  56. package/oauth-app/useDeleteOAuthApp.d.ts +31 -0
  57. package/oauth-app/useDeleteOAuthApp.d.ts.map +1 -0
  58. package/oauth-app/useDeleteOAuthApp.js +43 -0
  59. package/oauth-app/useDeleteOAuthApp.js.map +1 -0
  60. package/oauth-app/useOAuthAppList.d.ts +32 -0
  61. package/oauth-app/useOAuthAppList.d.ts.map +1 -0
  62. package/oauth-app/useOAuthAppList.js +61 -0
  63. package/oauth-app/useOAuthAppList.js.map +1 -0
  64. package/oauth-app/useUpdateOAuthApp.d.ts +38 -0
  65. package/oauth-app/useUpdateOAuthApp.d.ts.map +1 -0
  66. package/oauth-app/useUpdateOAuthApp.js +49 -0
  67. package/oauth-app/useUpdateOAuthApp.js.map +1 -0
  68. package/package.json +4 -4
  69. package/src/demo/fixtures.ts +8 -0
  70. package/src/demo/samples.ts +2 -0
  71. package/src/execution/ArtifactPreviewModal.tsx +206 -128
  72. package/src/execution/index.ts +2 -2
  73. package/src/index.ts +24 -0
  74. package/src/library/ResourceListView.tsx +8 -8
  75. package/src/mcp-server/McpServerConnectDialog.tsx +527 -0
  76. package/src/mcp-server/McpServerDetailView.tsx +2 -1
  77. package/src/mcp-server/McpServerPicker.tsx +8 -1
  78. package/src/mcp-server/index.ts +3 -0
  79. package/src/oauth-app/CreateOAuthAppForm.tsx +449 -0
  80. package/src/oauth-app/OAuthAppDetailPanel.tsx +671 -0
  81. package/src/oauth-app/OAuthAppListPanel.tsx +237 -0
  82. package/src/oauth-app/index.ts +14 -0
  83. package/src/oauth-app/useCreateOAuthApp.ts +70 -0
  84. package/src/oauth-app/useDeleteOAuthApp.ts +62 -0
  85. package/src/oauth-app/useOAuthAppList.ts +84 -0
  86. package/src/oauth-app/useUpdateOAuthApp.ts +69 -0
  87. package/styles.css +1 -1
@@ -0,0 +1,449 @@
1
+ "use client";
2
+
3
+ import { useCallback, useState, type FormEvent } from "react";
4
+ import { cn } from "@stigmer/theme";
5
+ import { getUserMessage } from "@stigmer/sdk";
6
+ import type { OAuthApp } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
7
+ import { VendorApprovalStatus } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/spec_pb";
8
+ import { useCreateOAuthApp } from "./useCreateOAuthApp";
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Public API
12
+ // ---------------------------------------------------------------------------
13
+
14
+ /** Props for {@link CreateOAuthAppForm}. */
15
+ export interface CreateOAuthAppFormProps {
16
+ /** Organization slug — the OAuth app will be created in this org. */
17
+ readonly org: string;
18
+ /** Fired with the newly created OAuth app on success. */
19
+ readonly onCreated?: (app: OAuthApp) => void;
20
+ /** Fired when the user cancels creation. */
21
+ readonly onCancel?: () => void;
22
+ /** Additional CSS class names for the root container. */
23
+ readonly className?: string;
24
+ }
25
+
26
+ /**
27
+ * Form for creating a new OAuth app within an organization.
28
+ *
29
+ * Collects the required OAuth configuration: **name**, **provider**,
30
+ * **client ID**, **client secret**, **authorization URL**, and
31
+ * **token URL**. An expandable "Advanced" section provides optional
32
+ * fields for scopes, userinfo URL, scope parameter name, and vendor
33
+ * approval settings.
34
+ *
35
+ * This is a pure presentational component with no dialog wrapper
36
+ * (headless-first). The parent is responsible for rendering it inside
37
+ * a card, dialog, or inline context as needed.
38
+ *
39
+ * All visual properties flow through `--stgm-*` design tokens.
40
+ *
41
+ * @example
42
+ * ```tsx
43
+ * <CreateOAuthAppForm
44
+ * org="acme"
45
+ * onCreated={(app) => {
46
+ * refetch();
47
+ * setShowForm(false);
48
+ * }}
49
+ * onCancel={() => setShowForm(false)}
50
+ * />
51
+ * ```
52
+ */
53
+ export function CreateOAuthAppForm({
54
+ org,
55
+ onCreated,
56
+ onCancel,
57
+ className,
58
+ }: CreateOAuthAppFormProps) {
59
+ const { create, isCreating, error, clearError } = useCreateOAuthApp();
60
+
61
+ const [name, setName] = useState("");
62
+ const [provider, setProvider] = useState("");
63
+ const [clientId, setClientId] = useState("");
64
+ const [clientSecret, setClientSecret] = useState("");
65
+ const [authorizationUrl, setAuthorizationUrl] = useState("");
66
+ const [tokenUrl, setTokenUrl] = useState("");
67
+
68
+ const [showAdvanced, setShowAdvanced] = useState(false);
69
+ const [scopes, setScopes] = useState("");
70
+ const [userinfoUrl, setUserinfoUrl] = useState("");
71
+ const [scopeParameterName, setScopeParameterName] = useState("");
72
+ const [vendorApprovalStatus, setVendorApprovalStatus] = useState<
73
+ "unspecified" | "pending" | "approved" | "rejected"
74
+ >("unspecified");
75
+ const [vendorApprovalDocsUrl, setVendorApprovalDocsUrl] = useState("");
76
+
77
+ const trimmedName = name.trim();
78
+ const trimmedProvider = provider.trim();
79
+ const trimmedClientId = clientId.trim();
80
+ const trimmedClientSecret = clientSecret.trim();
81
+ const trimmedAuthUrl = authorizationUrl.trim();
82
+ const trimmedTokenUrl = tokenUrl.trim();
83
+
84
+ const canSubmit =
85
+ trimmedName !== "" &&
86
+ trimmedProvider !== "" &&
87
+ trimmedClientId !== "" &&
88
+ trimmedClientSecret !== "" &&
89
+ trimmedAuthUrl !== "" &&
90
+ trimmedTokenUrl !== "" &&
91
+ !isCreating;
92
+
93
+ const handleSubmit = useCallback(
94
+ async (e: FormEvent) => {
95
+ e.preventDefault();
96
+ if (!canSubmit) return;
97
+
98
+ clearError();
99
+ try {
100
+ const parsedScopes = scopes
101
+ .split(",")
102
+ .map((s) => s.trim())
103
+ .filter(Boolean);
104
+
105
+ const app = await create({
106
+ name: trimmedName,
107
+ org,
108
+ provider: trimmedProvider,
109
+ clientId: trimmedClientId,
110
+ clientSecret: trimmedClientSecret,
111
+ authorizationUrl: trimmedAuthUrl,
112
+ tokenUrl: trimmedTokenUrl,
113
+ ...(parsedScopes.length > 0 && { scopes: parsedScopes }),
114
+ ...(userinfoUrl.trim() && { userinfoUrl: userinfoUrl.trim() }),
115
+ ...(scopeParameterName.trim() && {
116
+ scopeParameterName: scopeParameterName.trim(),
117
+ }),
118
+ ...(vendorApprovalStatus !== "unspecified" && {
119
+ vendorApprovalStatus: APPROVAL_STATUS_MAP[vendorApprovalStatus],
120
+ }),
121
+ ...(vendorApprovalDocsUrl.trim() && {
122
+ vendorApprovalDocsUrl: vendorApprovalDocsUrl.trim(),
123
+ }),
124
+ });
125
+ onCreated?.(app);
126
+ } catch {
127
+ // error state is managed by useCreateOAuthApp
128
+ }
129
+ },
130
+ [
131
+ canSubmit,
132
+ trimmedName,
133
+ org,
134
+ trimmedProvider,
135
+ trimmedClientId,
136
+ trimmedClientSecret,
137
+ trimmedAuthUrl,
138
+ trimmedTokenUrl,
139
+ scopes,
140
+ userinfoUrl,
141
+ scopeParameterName,
142
+ vendorApprovalStatus,
143
+ vendorApprovalDocsUrl,
144
+ create,
145
+ clearError,
146
+ onCreated,
147
+ ],
148
+ );
149
+
150
+ return (
151
+ <form onSubmit={handleSubmit} className={cn("space-y-3", className)}>
152
+ <div className="space-y-3">
153
+ <FormField
154
+ id="stgm-oauth-name"
155
+ label="Name"
156
+ value={name}
157
+ onChange={setName}
158
+ placeholder="e.g. My Slack App"
159
+ disabled={isCreating}
160
+ required
161
+ />
162
+
163
+ <FormField
164
+ id="stgm-oauth-provider"
165
+ label="Provider"
166
+ value={provider}
167
+ onChange={setProvider}
168
+ placeholder="e.g. Slack, GitHub, Salesforce"
169
+ hint="Human-readable vendor name for display"
170
+ disabled={isCreating}
171
+ required
172
+ />
173
+
174
+ <FormField
175
+ id="stgm-oauth-client-id"
176
+ label="Client ID"
177
+ value={clientId}
178
+ onChange={setClientId}
179
+ placeholder="OAuth client identifier"
180
+ disabled={isCreating}
181
+ required
182
+ />
183
+
184
+ <FormField
185
+ id="stgm-oauth-client-secret"
186
+ label="Client secret"
187
+ value={clientSecret}
188
+ onChange={setClientSecret}
189
+ placeholder="OAuth client secret"
190
+ type="password"
191
+ disabled={isCreating}
192
+ required
193
+ />
194
+
195
+ <FormField
196
+ id="stgm-oauth-auth-url"
197
+ label="Authorization URL"
198
+ value={authorizationUrl}
199
+ onChange={setAuthorizationUrl}
200
+ placeholder="https://vendor.com/oauth/authorize"
201
+ hint="Vendor's OAuth authorization endpoint"
202
+ disabled={isCreating}
203
+ required
204
+ />
205
+
206
+ <FormField
207
+ id="stgm-oauth-token-url"
208
+ label="Token URL"
209
+ value={tokenUrl}
210
+ onChange={setTokenUrl}
211
+ placeholder="https://vendor.com/oauth/token"
212
+ hint="Vendor's OAuth token exchange endpoint"
213
+ disabled={isCreating}
214
+ required
215
+ />
216
+
217
+ {/* Advanced section — collapsed by default */}
218
+ <div>
219
+ <button
220
+ type="button"
221
+ onClick={() => setShowAdvanced((v) => !v)}
222
+ className="text-muted-foreground hover:text-foreground flex items-center gap-1 text-[0.65rem] font-medium transition-colors"
223
+ >
224
+ <ChevronIcon expanded={showAdvanced} />
225
+ Advanced settings
226
+ </button>
227
+
228
+ {showAdvanced && (
229
+ <div className="mt-2 space-y-3 border-l-2 border-border/60 pl-3">
230
+ <FormField
231
+ id="stgm-oauth-scopes"
232
+ label="Scopes"
233
+ value={scopes}
234
+ onChange={setScopes}
235
+ placeholder="read, write, admin"
236
+ hint="Comma-separated OAuth scopes to request"
237
+ disabled={isCreating}
238
+ />
239
+
240
+ <FormField
241
+ id="stgm-oauth-userinfo-url"
242
+ label="Userinfo URL"
243
+ value={userinfoUrl}
244
+ onChange={setUserinfoUrl}
245
+ placeholder="https://vendor.com/userinfo"
246
+ hint="OIDC endpoint for fetching user profile data (optional)"
247
+ disabled={isCreating}
248
+ />
249
+
250
+ <FormField
251
+ id="stgm-oauth-scope-param"
252
+ label="Scope parameter name"
253
+ value={scopeParameterName}
254
+ onChange={setScopeParameterName}
255
+ placeholder="scope"
256
+ hint='Defaults to "scope". Some vendors use a non-standard name (e.g. "user_scope" for Slack).'
257
+ disabled={isCreating}
258
+ />
259
+
260
+ <div className="space-y-1">
261
+ <label
262
+ htmlFor="stgm-oauth-approval-status"
263
+ className="text-xs font-medium text-foreground"
264
+ >
265
+ Vendor approval status
266
+ </label>
267
+ <select
268
+ id="stgm-oauth-approval-status"
269
+ value={vendorApprovalStatus}
270
+ onChange={(e) =>
271
+ setVendorApprovalStatus(
272
+ e.target.value as typeof vendorApprovalStatus,
273
+ )
274
+ }
275
+ disabled={isCreating}
276
+ className={cn(
277
+ "w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs text-foreground",
278
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
279
+ "disabled:pointer-events-none disabled:opacity-50",
280
+ )}
281
+ >
282
+ <option value="unspecified">Unspecified (treated as approved)</option>
283
+ <option value="pending">Pending</option>
284
+ <option value="approved">Approved</option>
285
+ <option value="rejected">Rejected</option>
286
+ </select>
287
+ <p className="text-[0.65rem] text-muted-foreground">
288
+ Vendor marketplace approval lifecycle status
289
+ </p>
290
+ </div>
291
+
292
+ <FormField
293
+ id="stgm-oauth-approval-docs"
294
+ label="Vendor approval docs URL"
295
+ value={vendorApprovalDocsUrl}
296
+ onChange={setVendorApprovalDocsUrl}
297
+ placeholder="https://docs.example.com/byoa"
298
+ hint="Help link shown when vendor approval is pending"
299
+ disabled={isCreating}
300
+ />
301
+ </div>
302
+ )}
303
+ </div>
304
+ </div>
305
+
306
+ {error && (
307
+ <p className="text-destructive text-[0.65rem]" role="alert">
308
+ {getUserMessage(error)}
309
+ </p>
310
+ )}
311
+
312
+ <div className="flex items-center gap-2">
313
+ <button
314
+ type="submit"
315
+ disabled={!canSubmit}
316
+ className={cn(
317
+ "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium",
318
+ "bg-primary text-primary-foreground hover:bg-primary/90",
319
+ "disabled:pointer-events-none disabled:opacity-40",
320
+ )}
321
+ >
322
+ {isCreating && <SpinnerIcon />}
323
+ Create OAuth app
324
+ </button>
325
+
326
+ {onCancel && (
327
+ <button
328
+ type="button"
329
+ onClick={onCancel}
330
+ disabled={isCreating}
331
+ className={cn(
332
+ "rounded-md px-3 py-1.5 text-xs",
333
+ "text-muted-foreground hover:text-foreground hover:bg-accent/50",
334
+ "disabled:pointer-events-none disabled:opacity-50",
335
+ )}
336
+ >
337
+ Cancel
338
+ </button>
339
+ )}
340
+ </div>
341
+ </form>
342
+ );
343
+ }
344
+
345
+ // ---------------------------------------------------------------------------
346
+ // Constants
347
+ // ---------------------------------------------------------------------------
348
+
349
+ const APPROVAL_STATUS_MAP = {
350
+ pending: VendorApprovalStatus.PENDING,
351
+ approved: VendorApprovalStatus.APPROVED,
352
+ rejected: VendorApprovalStatus.REJECTED,
353
+ } as const;
354
+
355
+ // ---------------------------------------------------------------------------
356
+ // FormField (internal)
357
+ // ---------------------------------------------------------------------------
358
+
359
+ function FormField({
360
+ id,
361
+ label,
362
+ value,
363
+ onChange,
364
+ placeholder,
365
+ hint,
366
+ type = "text",
367
+ disabled,
368
+ required,
369
+ }: {
370
+ id: string;
371
+ label: string;
372
+ value: string;
373
+ onChange: (v: string) => void;
374
+ placeholder: string;
375
+ hint?: string;
376
+ type?: "text" | "password";
377
+ disabled: boolean;
378
+ required?: boolean;
379
+ }) {
380
+ return (
381
+ <div className="space-y-1">
382
+ <label htmlFor={id} className="text-xs font-medium text-foreground">
383
+ {label}
384
+ </label>
385
+ <input
386
+ id={id}
387
+ type={type}
388
+ value={value}
389
+ onChange={(e) => onChange(e.target.value)}
390
+ placeholder={placeholder}
391
+ disabled={disabled}
392
+ required={required}
393
+ className={cn(
394
+ "w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs text-foreground",
395
+ "placeholder:text-muted-foreground",
396
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
397
+ "disabled:pointer-events-none disabled:opacity-50",
398
+ )}
399
+ />
400
+ {hint && (
401
+ <p className="text-[0.65rem] text-muted-foreground">{hint}</p>
402
+ )}
403
+ </div>
404
+ );
405
+ }
406
+
407
+ // ---------------------------------------------------------------------------
408
+ // Icons
409
+ // ---------------------------------------------------------------------------
410
+
411
+ function ChevronIcon({ expanded }: { expanded: boolean }) {
412
+ return (
413
+ <svg
414
+ width="10"
415
+ height="10"
416
+ viewBox="0 0 16 16"
417
+ fill="none"
418
+ stroke="currentColor"
419
+ strokeWidth="2"
420
+ strokeLinecap="round"
421
+ strokeLinejoin="round"
422
+ aria-hidden="true"
423
+ className={cn(
424
+ "shrink-0 transition-transform",
425
+ expanded && "rotate-90",
426
+ )}
427
+ >
428
+ <path d="M6 4l4 4-4 4" />
429
+ </svg>
430
+ );
431
+ }
432
+
433
+ function SpinnerIcon() {
434
+ return (
435
+ <svg
436
+ width="12"
437
+ height="12"
438
+ viewBox="0 0 16 16"
439
+ fill="none"
440
+ stroke="currentColor"
441
+ strokeWidth="2"
442
+ strokeLinecap="round"
443
+ className="animate-spin"
444
+ aria-hidden="true"
445
+ >
446
+ <path d="M8 2a6 6 0 1 0 6 6" />
447
+ </svg>
448
+ );
449
+ }