Skip to content

Commit c0b7eef

Browse files
committed
Scope Integrations to the signed-in member and their credentials
1 parent 37574ce commit c0b7eef

72 files changed

Lines changed: 1096 additions & 2737 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/sim/platform/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ SIM_PLATFORM_FIXTURES=1 bun run --cwd apps/sim dev:platform
1313

1414
Open [Home](http://localhost:3100/home), [Chat](http://localhost:3100/chat/weekly-review?state=full), [Settings](http://localhost:3100/settings), [Integrations](http://localhost:3100/integrations?state=full), or the separate [component workbench](http://localhost:3100/workbench). With the flag on, `?state=empty|full|stress` selects the fixture on any of them. `?theme=light|dark|system` selects the product appearance. Product navigation preserves these choices; nothing writes examples to storage. The URL cannot activate the server flag. Old catalog links with a recipe still open the workbench.
1515

16-
The product routes live under `app/(shell)/`. Their shared App Router layout owns the shell, sidebar scroll viewport, interface preferences and chat edits. Home, Chat, Settings and Integrations pages render their own bodies inside that layout. Navigating between them keeps the sidebar reading position, including when returning from Settings. The shell selects its gated fixture and appearance from the current URL. Each route owns its components and CSS Modules in a private `_components/<component>/` folder: the [Home chart](app/%28shell%29/home/_components/activity-chart/activity-chart.tsx) sits beside its styles; the [dialog](design-system/components/modal/modal.tsx) belongs to EMCN. Shared shell UI lives in `app/(shell)/_components/`. [Rhythm tokens](design-system/styles/rhythm.css) and the [ownership and interaction contract](AGENTS.md) apply throughout the product. The [Paper state inventory](docs/core-chat-states.md) enumerates the source rows in scope. The [organization integrations guide](docs/organization-integrations.md) maps the app catalog, Search, account controls and workspace grants to their Phase 3 operations. Workspace destinations and workspace create/sort return to Home. Wiki is deferred.
16+
The product routes live under `app/(shell)/`. Their shared App Router layout owns the shell, sidebar scroll viewport, interface preferences and chat edits. Home, Chat, Settings and Integrations pages render their own bodies inside that layout. Navigating between them keeps the sidebar reading position, including when returning from Settings. The shell selects its gated fixture and appearance from the current URL. Each route owns its components and CSS Modules in a private `_components/<component>/` folder: the [Home chart](app/%28shell%29/home/_components/activity-chart/activity-chart.tsx) sits beside its styles; the [dialog](design-system/components/modal/modal.tsx) belongs to EMCN. Shared shell UI lives in `app/(shell)/_components/`. [Rhythm tokens](design-system/styles/rhythm.css) and the [ownership and interaction contract](AGENTS.md) apply throughout the product. The [Paper state inventory](docs/core-chat-states.md) enumerates the source rows in scope. The [organization integrations guide](docs/organization-integrations.md) maps the member inventory, credential actions and tool permissions to their Phase 3 operations. Workspace destinations and workspace create/sort return to Home. Wiki is deferred.
1717

1818
## Desktop
1919

‎apps/sim/platform/app/(shell)/_components/product-session.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function ProductSession({ children, fixtures }: ProductSessionProps) {
3131
<ChatChangesProvider>
3232
<ComposerDrafts>
3333
<ProductSurface preference={readAppearance(search.get('theme'))}>
34-
<IntegrationChanges key={search.get('role') === 'member' ? 'member' : 'admin'}>
34+
<IntegrationChanges>
3535
<PlatformShell data={fixtures?.[scenario] ?? null}>{children}</PlatformShell>
3636
</IntegrationChanges>
3737
</ProductSurface>
Lines changed: 26 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
'use client'
22

33
import { createContext, type ReactNode, useContext, useState } from 'react'
4-
import {
5-
type IntegrationsData,
6-
memberSearchMethod,
7-
type OrganizationIntegration,
8-
type WorkspaceAccess,
9-
} from '@/platform/lib/integrations/model'
4+
import type { IntegrationsData, OrganizationIntegration } from '@/platform/lib/integrations/model'
5+
6+
type AccountEdits = Pick<
7+
OrganizationIntegration,
8+
'accounts' | 'connectableMethod' | 'connectionCandidate'
9+
>
1010

1111
function useEdits() {
12-
const [records, setRecords] = useState<Record<string, OrganizationIntegration | null>>({})
13-
const [grants, setGrants] = useState<Record<string, WorkspaceAccess> | null>(null)
14-
return { records, setRecords, grants, setGrants }
12+
return useState<Record<string, AccountEdits>>({})
1513
}
1614

1715
const Changes = createContext<ReturnType<typeof useEdits> | null>(null)
@@ -21,71 +19,29 @@ interface IntegrationChangesProps {
2119
}
2220

2321
export function IntegrationChanges({ children }: IntegrationChangesProps) {
24-
return <Changes.Provider value={useEdits()}>{children}</Changes.Provider>
22+
const edits = useEdits()
23+
return <Changes.Provider value={edits}>{children}</Changes.Provider>
2524
}
2625

2726
export function useOrganizationIntegrations(data: IntegrationsData | null) {
28-
const changes = useContext(Changes)
29-
if (!changes) throw new Error('Integration controls must be inside the product layout')
30-
const { records, setRecords, grants, setGrants } = changes
31-
const configured = new Map(data?.integrations.map((item) => [item.providerId, item]))
32-
for (const [id, edit] of Object.entries(records)) {
33-
if (edit) configured.set(id, edit)
34-
else configured.delete(id)
35-
}
36-
function save(next: OrganizationIntegration) {
37-
if (!data) return
38-
const current = configured.get(next.providerId)
39-
if (!data.canManage && !current) return
40-
const provider = data.providers.find((entry) => entry.id === next.providerId)
41-
if (!provider) return
42-
const requiredMethod = next.searchApproved && memberSearchMethod(provider)
43-
const updated =
44-
requiredMethod && !next.accountMethods.includes(requiredMethod)
45-
? { ...next, accountMethods: [...next.accountMethods, requiredMethod] }
46-
: next
47-
const previouslyActive = new Set(
48-
current?.people.filter((person) => person.status !== 'revoked').map((person) => person.id)
49-
)
50-
const revoked = new Set(
51-
next.people
52-
.filter((person) => person.status === 'revoked' && previouslyActive.has(person.id))
53-
.map((person) => person.id)
54-
)
55-
setRecords((previous) => {
56-
const updates = { ...previous }
57-
if (data.canManage && revoked.size) {
58-
for (const item of configured.values()) {
59-
if (item.people.some((person) => revoked.has(person.id) && person.status !== 'revoked')) {
60-
updates[item.providerId] = {
61-
...item,
62-
people: item.people.map((person) =>
63-
revoked.has(person.id) ? { ...person, status: 'revoked' } : person
64-
),
65-
}
66-
}
67-
}
68-
}
69-
updates[next.providerId] =
70-
data.canManage || !current ? updated : { ...current, accounts: next.accounts }
71-
return updates
72-
})
73-
}
27+
const edits = useContext(Changes)
28+
if (!edits) throw new Error('Integration controls must be inside the product layout')
29+
const [accounts, setAccounts] = edits
7430
return {
75-
configured,
76-
grants: grants ?? data?.workspaceGrants ?? {},
77-
save,
78-
remove(id: string) {
79-
if (data?.canManage) setRecords((previous) => ({ ...previous, [id]: null }))
80-
},
81-
setAccess(workspaceId: string, access: WorkspaceAccess | null) {
82-
if (!data?.canManage) return
83-
setGrants((previous) => {
84-
const next = { ...(previous ?? data.workspaceGrants) }
85-
if (access) next[workspaceId] = access
86-
else delete next[workspaceId]
87-
return next
88-
})
31+
integrations: (data?.integrations ?? []).map((item) => ({
32+
...item,
33+
...accounts[item.providerId],
34+
})),
35+
save(item: OrganizationIntegration) {
36+
if (!data?.integrations.some(({ providerId }) => providerId === item.providerId)) return
37+
setAccounts((current) => ({
38+
...current,
39+
[item.providerId]: {
40+
accounts: item.accounts,
41+
connectableMethod: item.connectableMethod,
42+
connectionCandidate: item.connectionCandidate,
43+
},
44+
}))
8945
},
9046
}
9147
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Modal } from '@/platform/design-system/components'
2+
import type { AccountMethod, IntegrationAccount } from '@/platform/lib/integrations/model'
3+
4+
const AUTHORIZATION: Record<AccountMethod, string> = {
5+
oauth: 'Authorize your provider account to give Sim access.',
6+
slack: 'Authorize your member account with your organization’s Slack app.',
7+
mcp: 'Sign in to the managed MCP server with your own provider account.',
8+
}
9+
10+
interface AccountAuthorizationProps {
11+
provider: string
12+
method: AccountMethod
13+
account?: IntegrationAccount
14+
candidateLabel?: string
15+
onClose: () => void
16+
onAuthorize?: () => void
17+
}
18+
19+
export function AccountAuthorization({
20+
provider,
21+
method,
22+
account,
23+
candidateLabel,
24+
onClose,
25+
onAuthorize,
26+
}: AccountAuthorizationProps) {
27+
const label = account?.label ?? candidateLabel
28+
return (
29+
<Modal
30+
open
31+
onOpenChange={onClose}
32+
title={account ? 'Reconnect account' : `Connect to ${provider}`}
33+
description={label ? `${label} · ${AUTHORIZATION[method]}` : AUTHORIZATION[method]}
34+
confirmLabel={account ? 'Reconnect' : 'Connect account'}
35+
confirmDisabled={!onAuthorize}
36+
onConfirm={onAuthorize}
37+
/>
38+
)
39+
}

‎apps/sim/platform/app/(shell)/integrations/_components/connection-fields/connection-fields.tsx‎

Lines changed: 0 additions & 67 deletions
This file was deleted.

0 commit comments

Comments
 (0)