Skip to content

Commit 59be51a

Browse files
Hide integration edit/delete controls for non-admin members (#2013)
Co-authored-by: Rhys Sullivan <rhys@rhyssullivan.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 5b5db98 commit 59be51a

1 file changed

Lines changed: 18 additions & 5 deletions

File tree

packages/react/src/pages/integration-detail.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { IntegrationEditSheet } from "../components/metadata-edit-sheet";
3737
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../components/tabs";
3838
import { authMethodsFromDescriptors, type AuthMethod } from "../lib/auth-placements";
3939
import { usePolicyActions } from "../hooks/use-policy-actions";
40+
import { useCanCreateWorkspaceConnections } from "../multiplayer/use-admin-nav";
4041
import { useIntegrationPlugins, type IntegrationAccountHandoff } from "@executor-js/sdk/client";
4142
import { Button } from "../components/button";
4243
import { Skeleton } from "../components/skeleton";
@@ -84,9 +85,13 @@ export function IntegrationDetailPage(props: {
8485
const connectionsResult = useAtomValue(connectionsAllAtom);
8586
const refreshIntegrations = useAtomRefresh(integrationsOptimisticAtom);
8687
const refreshTools = useAtomRefresh(integrationToolsAllAtom(slug));
87-
const doRemove = useAtomSet(removeIntegrationOptimistic, { mode: "promiseExit" });
88+
const doRemove = useAtomSet(removeIntegrationOptimistic, {
89+
mode: "promiseExit",
90+
});
8891
const doRefresh = useAtomSet(refreshConnection, { mode: "promiseExit" });
89-
const doCheckHealth = useAtomSet(checkConnectionHealth, { mode: "promiseExit" });
92+
const doCheckHealth = useAtomSet(checkConnectionHealth, {
93+
mode: "promiseExit",
94+
});
9095
// Policies are owner-partitioned on write; the integration policy menu writes
9196
// Workspace (org) rules, preserving the prior default behavior.
9297
const policyActions = usePolicyActions("org");
@@ -133,8 +138,12 @@ export function IntegrationDetailPage(props: {
133138
useExecutorDocumentTitle(integrationData?.name || namespace);
134139
const isBuiltInIntegration = namespace === "executor" || integrationData?.kind === "built-in";
135140
const currentTab = isBuiltInIntegration ? "tools" : activeTab;
141+
// Integrations are workspace-owned; the server refuses catalog mutations
142+
// (update/remove) from non-admin members, so hide the controls for them.
143+
const canMutateIntegration = useCanCreateWorkspaceConnections();
144+
const canEdit = canMutateIntegration && !isBuiltInIntegration && integrationData !== null;
136145
const canRefresh = integrationData?.canRefresh ?? false;
137-
const canRemove = integrationData?.canRemove ?? false;
146+
const canRemove = canMutateIntegration && (integrationData?.canRemove ?? false);
138147
const urlAccountHandoff = useMemo<IntegrationAccountHandoff | null>(() => {
139148
const search = new URLSearchParams(locationSearch);
140149
// The route-validated flag and the raw `addAccount=1` are the same request;
@@ -389,7 +398,11 @@ export function IntegrationDetailPage(props: {
389398
if (connection.integration !== slug) continue;
390399
connectionCount++;
391400
const refreshExit = await doRefresh({
392-
params: { owner: connection.owner, integration: slug, name: connection.name },
401+
params: {
402+
owner: connection.owner,
403+
integration: slug,
404+
name: connection.name,
405+
},
393406
reactivityKeys: connectionWriteKeys,
394407
});
395408
refreshExits.push(Exit.isSuccess(refreshExit));
@@ -485,7 +498,7 @@ export function IntegrationDetailPage(props: {
485498
</div>
486499

487500
<div className="flex shrink-0 items-center gap-2">
488-
{!confirmDelete && !isBuiltInIntegration && integrationData && (
501+
{!confirmDelete && canEdit && (
489502
<Button variant="outline" size="sm" onClick={() => setEditSheetOpen(true)}>
490503
Edit
491504
</Button>

0 commit comments

Comments
 (0)