@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,237 @@
1
+ "use client";
2
+
3
+ import { cn } from "@stigmer/theme";
4
+ import { getUserMessage } from "@stigmer/sdk";
5
+ import { timestampDate } from "@bufbuild/protobuf/wkt";
6
+ import type { OAuthApp } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
7
+ import { useOAuthAppList } from "./useOAuthAppList";
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Public API
11
+ // ---------------------------------------------------------------------------
12
+
13
+ /** Props for {@link OAuthAppListPanel}. */
14
+ export interface OAuthAppListPanelProps {
15
+ /** Organization slug to list OAuth apps for. */
16
+ readonly org: string;
17
+ /**
18
+ * Fired when the user wants to view/edit an OAuth app.
19
+ * When provided, rows become interactive with a pencil icon button.
20
+ * When absent, rows remain static (backward compatible).
21
+ */
22
+ readonly onEdit?: (app: OAuthApp) => void;
23
+ /** Re-expose refetch so parents can trigger a list refresh. */
24
+ readonly onRefetchRef?: (refetch: () => void) => void;
25
+ /** Additional CSS class names for the root container. */
26
+ readonly className?: string;
27
+ }
28
+
29
+ /**
30
+ * Displays a list of {@link OAuthApp} resources owned by an
31
+ * organization.
32
+ *
33
+ * Each row shows the provider name, client ID (non-secret), and
34
+ * creation date. When `onEdit` is provided, rows include a pencil
35
+ * icon that fires the callback with the selected app — enabling
36
+ * navigation to a detail/edit view.
37
+ *
38
+ * All visual properties flow through `--stgm-*` design tokens.
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * <OAuthAppListPanel org="acme" />
43
+ * ```
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * <OAuthAppListPanel
48
+ * org="acme"
49
+ * onEdit={(app) => setFlow({ phase: "editing", oauthApp: app })}
50
+ * onRefetchRef={(refetch) => { listRefetchRef.current = refetch; }}
51
+ * />
52
+ * ```
53
+ */
54
+ export function OAuthAppListPanel({
55
+ org,
56
+ onEdit,
57
+ onRefetchRef,
58
+ className,
59
+ }: OAuthAppListPanelProps) {
60
+ const { oauthApps, isLoading, error, refetch } = useOAuthAppList(org);
61
+
62
+ if (onRefetchRef) {
63
+ onRefetchRef(refetch);
64
+ }
65
+
66
+ if (isLoading) {
67
+ return (
68
+ <div
69
+ className={cn("space-y-2", className)}
70
+ aria-busy="true"
71
+ aria-label="Loading OAuth apps"
72
+ >
73
+ {Array.from({ length: 2 }, (_, i) => (
74
+ <div
75
+ key={i}
76
+ className="bg-muted/40 h-14 animate-pulse rounded-lg"
77
+ />
78
+ ))}
79
+ </div>
80
+ );
81
+ }
82
+
83
+ if (error) {
84
+ return (
85
+ <p className={cn("text-destructive text-xs", className)} role="alert">
86
+ {getUserMessage(error)}
87
+ </p>
88
+ );
89
+ }
90
+
91
+ if (oauthApps.length === 0) {
92
+ return (
93
+ <p
94
+ className={cn(
95
+ "text-muted-foreground py-4 text-center text-xs",
96
+ className,
97
+ )}
98
+ >
99
+ No OAuth apps configured yet.
100
+ </p>
101
+ );
102
+ }
103
+
104
+ return (
105
+ <div
106
+ className={cn("space-y-2", className)}
107
+ role="list"
108
+ aria-label="OAuth apps"
109
+ >
110
+ {oauthApps.map((app) => (
111
+ <OAuthAppRow
112
+ key={app.metadata?.id ?? ""}
113
+ oauthApp={app}
114
+ onEdit={onEdit ? () => onEdit(app) : undefined}
115
+ />
116
+ ))}
117
+ </div>
118
+ );
119
+ }
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // OAuthAppRow (internal)
123
+ // ---------------------------------------------------------------------------
124
+
125
+ function OAuthAppRow({
126
+ oauthApp,
127
+ onEdit,
128
+ }: {
129
+ oauthApp: OAuthApp;
130
+ onEdit?: () => void;
131
+ }) {
132
+ const provider = oauthApp.spec?.provider || "OAuth App";
133
+ const clientId = oauthApp.spec?.clientId;
134
+ const createdAt = oauthApp.status?.audit?.specAudit?.createdAt;
135
+
136
+ return (
137
+ <div
138
+ role="listitem"
139
+ className="flex items-center gap-3 rounded-lg border border-border/60 px-3 py-2.5 transition-colors hover:border-border"
140
+ >
141
+ <OAuthIcon />
142
+
143
+ <div className="min-w-0 flex-1">
144
+ <span className="block truncate text-sm font-medium text-foreground">
145
+ {provider}
146
+ </span>
147
+ {clientId && (
148
+ <span className="block truncate text-xs font-mono text-muted-foreground">
149
+ {clientId}
150
+ </span>
151
+ )}
152
+ </div>
153
+
154
+ <div className="hidden shrink-0 items-center gap-4 text-xs text-muted-foreground sm:flex">
155
+ {createdAt && (
156
+ <span title={`Created ${timestampDate(createdAt).toISOString()}`}>
157
+ {formatShortDate(timestampDate(createdAt))}
158
+ </span>
159
+ )}
160
+ </div>
161
+
162
+ {onEdit && (
163
+ <button
164
+ type="button"
165
+ onClick={onEdit}
166
+ aria-label={`Edit ${provider}`}
167
+ className={cn(
168
+ "shrink-0 rounded p-1",
169
+ "text-muted-foreground hover:text-foreground hover:bg-accent/50",
170
+ "transition-colors",
171
+ )}
172
+ >
173
+ <PencilIcon />
174
+ </button>
175
+ )}
176
+ </div>
177
+ );
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // Icons (edit action)
182
+ // ---------------------------------------------------------------------------
183
+
184
+ function PencilIcon() {
185
+ return (
186
+ <svg
187
+ width="14"
188
+ height="14"
189
+ viewBox="0 0 16 16"
190
+ fill="none"
191
+ stroke="currentColor"
192
+ strokeWidth="1.5"
193
+ strokeLinecap="round"
194
+ strokeLinejoin="round"
195
+ aria-hidden="true"
196
+ >
197
+ <path d="M11 2.5l2.5 2.5L5 13.5H2.5V11L11 2.5z" />
198
+ </svg>
199
+ );
200
+ }
201
+
202
+ // ---------------------------------------------------------------------------
203
+ // Formatting helpers
204
+ // ---------------------------------------------------------------------------
205
+
206
+ function formatShortDate(date: Date): string {
207
+ return date.toLocaleDateString(undefined, {
208
+ month: "short",
209
+ day: "numeric",
210
+ year: "numeric",
211
+ });
212
+ }
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // Icons
216
+ // ---------------------------------------------------------------------------
217
+
218
+ function OAuthIcon() {
219
+ return (
220
+ <svg
221
+ width="14"
222
+ height="14"
223
+ viewBox="0 0 16 16"
224
+ fill="none"
225
+ stroke="currentColor"
226
+ strokeWidth="1.5"
227
+ strokeLinecap="round"
228
+ strokeLinejoin="round"
229
+ aria-hidden="true"
230
+ className="shrink-0 text-muted-foreground"
231
+ >
232
+ <rect x="2" y="3" width="12" height="10" rx="2" />
233
+ <path d="M8 7v2" />
234
+ <circle cx="8" cy="9.5" r="0.5" fill="currentColor" stroke="none" />
235
+ </svg>
236
+ );
237
+ }
@@ -0,0 +1,14 @@
1
+ export { useOAuthAppList } from "./useOAuthAppList";
2
+ export type { UseOAuthAppListReturn } from "./useOAuthAppList";
3
+ export { useCreateOAuthApp } from "./useCreateOAuthApp";
4
+ export type { UseCreateOAuthAppReturn } from "./useCreateOAuthApp";
5
+ export { useUpdateOAuthApp } from "./useUpdateOAuthApp";
6
+ export type { UseUpdateOAuthAppReturn } from "./useUpdateOAuthApp";
7
+ export { useDeleteOAuthApp } from "./useDeleteOAuthApp";
8
+ export type { UseDeleteOAuthAppReturn } from "./useDeleteOAuthApp";
9
+ export { OAuthAppListPanel } from "./OAuthAppListPanel";
10
+ export type { OAuthAppListPanelProps } from "./OAuthAppListPanel";
11
+ export { CreateOAuthAppForm } from "./CreateOAuthAppForm";
12
+ export type { CreateOAuthAppFormProps } from "./CreateOAuthAppForm";
13
+ export { OAuthAppDetailPanel } from "./OAuthAppDetailPanel";
14
+ export type { OAuthAppDetailPanelProps } from "./OAuthAppDetailPanel";
@@ -0,0 +1,70 @@
1
+ "use client";
2
+
3
+ import { useCallback, useState } from "react";
4
+ import type { OAuthAppInput } from "@stigmer/sdk";
5
+ import type { OAuthApp } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
6
+ import { useStigmer } from "../hooks";
7
+ import { toError } from "../internal/toError";
8
+
9
+ /** Return value of {@link useCreateOAuthApp}. */
10
+ export interface UseCreateOAuthAppReturn {
11
+ /** Submit an {@link OAuthAppInput} to create a new OAuth app. Resolves with the server-created resource. */
12
+ readonly create: (input: OAuthAppInput) => Promise<OAuthApp>;
13
+ /** `true` while the create request is in flight. */
14
+ readonly isCreating: boolean;
15
+ /** Error from the last failed create, or `null` when healthy. */
16
+ readonly error: Error | null;
17
+ /** Reset `error` to `null`. */
18
+ readonly clearError: () => void;
19
+ }
20
+
21
+ /**
22
+ * Mutation hook that wraps `oauthapp.create()` with loading and error
23
+ * state.
24
+ *
25
+ * Creates an OAuth app resource within an organization. The caller
26
+ * provides an {@link OAuthAppInput} with the required metadata (name,
27
+ * org) and spec fields (provider, client ID, client secret, OAuth
28
+ * endpoint URLs).
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * const { create, isCreating, error } = useCreateOAuthApp();
33
+ *
34
+ * const app = await create({
35
+ * name: "My Slack App",
36
+ * org: "acme",
37
+ * provider: "Slack",
38
+ * clientId: "123456.789012",
39
+ * clientSecret: "secret",
40
+ * authorizationUrl: "https://slack.com/oauth/v2/authorize",
41
+ * tokenUrl: "https://slack.com/api/oauth.v2.access",
42
+ * });
43
+ * ```
44
+ */
45
+ export function useCreateOAuthApp(): UseCreateOAuthAppReturn {
46
+ const stigmer = useStigmer();
47
+ const [isCreating, setIsCreating] = useState(false);
48
+ const [error, setError] = useState<Error | null>(null);
49
+
50
+ const clearError = useCallback(() => setError(null), []);
51
+
52
+ const create = useCallback(
53
+ async (input: OAuthAppInput): Promise<OAuthApp> => {
54
+ setIsCreating(true);
55
+ setError(null);
56
+
57
+ try {
58
+ return await stigmer.oauthapp.create(input);
59
+ } catch (err) {
60
+ setError(toError(err));
61
+ throw err;
62
+ } finally {
63
+ setIsCreating(false);
64
+ }
65
+ },
66
+ [stigmer],
67
+ );
68
+
69
+ return { create, isCreating, error, clearError };
70
+ }
@@ -0,0 +1,62 @@
1
+ "use client";
2
+
3
+ import { useCallback, useState } from "react";
4
+ import type { OAuthApp } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
5
+ import { useStigmer } from "../hooks";
6
+ import { toError } from "../internal/toError";
7
+
8
+ /** Return value of {@link useDeleteOAuthApp}. */
9
+ export interface UseDeleteOAuthAppReturn {
10
+ /** Delete an OAuth app by its resource ID. Resolves with the deleted resource for confirmation display. */
11
+ readonly deleteApp: (id: string) => Promise<OAuthApp>;
12
+ /** `true` while the delete request is in flight. */
13
+ readonly isDeleting: boolean;
14
+ /** Error from the last failed delete, or `null` when healthy. */
15
+ readonly error: Error | null;
16
+ /** Reset `error` to `null`. */
17
+ readonly clearError: () => void;
18
+ }
19
+
20
+ /**
21
+ * Mutation hook that wraps `oauthapp.delete()` with loading and error
22
+ * state.
23
+ *
24
+ * Deletes an OAuth app by its resource ID. Returns the deleted
25
+ * {@link OAuthApp} on success so callers can confirm which app was
26
+ * removed. The deletion is permanent — any MCP server overrides
27
+ * referencing this app will lose their binding.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * const { deleteApp, isDeleting, error } = useDeleteOAuthApp();
32
+ *
33
+ * await deleteApp("oauth-app-id-abc123");
34
+ * refetch(); // refresh the list after deletion
35
+ * ```
36
+ */
37
+ export function useDeleteOAuthApp(): UseDeleteOAuthAppReturn {
38
+ const stigmer = useStigmer();
39
+ const [isDeleting, setIsDeleting] = useState(false);
40
+ const [error, setError] = useState<Error | null>(null);
41
+
42
+ const clearError = useCallback(() => setError(null), []);
43
+
44
+ const deleteApp = useCallback(
45
+ async (id: string): Promise<OAuthApp> => {
46
+ setIsDeleting(true);
47
+ setError(null);
48
+
49
+ try {
50
+ return await stigmer.oauthapp.delete({ resourceId: id });
51
+ } catch (err) {
52
+ setError(toError(err));
53
+ throw err;
54
+ } finally {
55
+ setIsDeleting(false);
56
+ }
57
+ },
58
+ [stigmer],
59
+ );
60
+
61
+ return { deleteApp, isDeleting, error, clearError };
62
+ }
@@ -0,0 +1,84 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useState } from "react";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import { ListOAuthAppsByOrgInputSchema } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/io_pb";
6
+ import type { OAuthApp } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
7
+ import { useStigmer } from "../hooks";
8
+ import { toError } from "../internal/toError";
9
+
10
+ /** Return value of {@link useOAuthAppList}. */
11
+ export interface UseOAuthAppListReturn {
12
+ /** OAuth apps owned by the organization. Empty while loading or on error. */
13
+ readonly oauthApps: readonly OAuthApp[];
14
+ /** `true` while the initial fetch or a refetch is in flight. */
15
+ readonly isLoading: boolean;
16
+ /** Error from the last failed request, or `null` when healthy. */
17
+ readonly error: Error | null;
18
+ /** Discard cached data and re-fetch the list from the server. */
19
+ readonly refetch: () => void;
20
+ }
21
+
22
+ /**
23
+ * Data hook that fetches all {@link OAuthApp} entries for an organization.
24
+ *
25
+ * Returns every OAuthApp whose `metadata.org` matches the input. In
26
+ * practice these are the BYOA OAuth apps created through the "Bring
27
+ * your own app" flow on MCP server detail pages.
28
+ *
29
+ * Pass `null` for `org` to skip fetching (stable no-op). Useful when
30
+ * the active organization has not been resolved yet.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * const { oauthApps, isLoading, error } = useOAuthAppList(org);
35
+ *
36
+ * if (isLoading) return <Spinner />;
37
+ * oauthApps.map((app) => app.spec?.provider);
38
+ * ```
39
+ */
40
+ export function useOAuthAppList(
41
+ org: string | null,
42
+ ): UseOAuthAppListReturn {
43
+ const stigmer = useStigmer();
44
+ const [oauthApps, setOauthApps] = useState<OAuthApp[]>([]);
45
+ const [isLoading, setIsLoading] = useState(false);
46
+ const [error, setError] = useState<Error | null>(null);
47
+ const [fetchKey, setFetchKey] = useState(0);
48
+
49
+ const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
50
+
51
+ useEffect(() => {
52
+ if (!org) {
53
+ setOauthApps([]);
54
+ setIsLoading(false);
55
+ setError(null);
56
+ return;
57
+ }
58
+
59
+ const cancelled = { current: false };
60
+ setIsLoading(true);
61
+ setError(null);
62
+
63
+ stigmer.oauthapp
64
+ .listByOrg(create(ListOAuthAppsByOrgInputSchema, { org }))
65
+ .then(
66
+ (result) => {
67
+ if (cancelled.current) return;
68
+ setOauthApps([...result.entries]);
69
+ setIsLoading(false);
70
+ },
71
+ (err) => {
72
+ if (cancelled.current) return;
73
+ setError(toError(err));
74
+ setIsLoading(false);
75
+ },
76
+ );
77
+
78
+ return () => {
79
+ cancelled.current = true;
80
+ };
81
+ }, [org, stigmer, fetchKey]);
82
+
83
+ return { oauthApps, isLoading, error, refetch };
84
+ }
@@ -0,0 +1,69 @@
1
+ "use client";
2
+
3
+ import { useCallback, useState } from "react";
4
+ import type { OAuthAppInput } from "@stigmer/sdk";
5
+ import type { OAuthApp } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
6
+ import { useStigmer } from "../hooks";
7
+ import { toError } from "../internal/toError";
8
+
9
+ /** Return value of {@link useUpdateOAuthApp}. */
10
+ export interface UseUpdateOAuthAppReturn {
11
+ /** Submit an {@link OAuthAppInput} to update an existing OAuth app. Resolves with the updated resource. */
12
+ readonly update: (input: OAuthAppInput) => Promise<OAuthApp>;
13
+ /** `true` while the update request is in flight. */
14
+ readonly isUpdating: boolean;
15
+ /** Error from the last failed update, or `null` when healthy. */
16
+ readonly error: Error | null;
17
+ /** Reset `error` to `null`. */
18
+ readonly clearError: () => void;
19
+ }
20
+
21
+ /**
22
+ * Mutation hook that wraps `oauthapp.update()` with loading and error
23
+ * state.
24
+ *
25
+ * Updates an existing OAuth app. The input must include the `name` and
26
+ * `org` fields to identify the target resource, along with the updated
27
+ * spec fields. Omit `clientSecret` to leave the existing secret
28
+ * unchanged.
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * const { update, isUpdating, error } = useUpdateOAuthApp();
33
+ *
34
+ * await update({
35
+ * name: "My Slack App",
36
+ * org: "acme",
37
+ * provider: "Slack",
38
+ * clientId: "123456.789012",
39
+ * authorizationUrl: "https://slack.com/oauth/v2/authorize",
40
+ * tokenUrl: "https://slack.com/api/oauth.v2.access",
41
+ * });
42
+ * ```
43
+ */
44
+ export function useUpdateOAuthApp(): UseUpdateOAuthAppReturn {
45
+ const stigmer = useStigmer();
46
+ const [isUpdating, setIsUpdating] = useState(false);
47
+ const [error, setError] = useState<Error | null>(null);
48
+
49
+ const clearError = useCallback(() => setError(null), []);
50
+
51
+ const update = useCallback(
52
+ async (input: OAuthAppInput): Promise<OAuthApp> => {
53
+ setIsUpdating(true);
54
+ setError(null);
55
+
56
+ try {
57
+ return await stigmer.oauthapp.update(input);
58
+ } catch (err) {
59
+ setError(toError(err));
60
+ throw err;
61
+ } finally {
62
+ setIsUpdating(false);
63
+ }
64
+ },
65
+ [stigmer],
66
+ );
67
+
68
+ return { update, isUpdating, error, clearError };
69
+ }