Skip to content

Commit fdcbf89

Browse files
improvement(settings): remove personal connected accounts page
1 parent a2f1ac7 commit fdcbf89

11 files changed

Lines changed: 51 additions & 304 deletions

File tree

apps/sim/app/account/settings/[section]/page.test.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ describe('account settings legacy links', () => {
5252
)
5353
})
5454

55-
it('still rejects unknown sections', async () => {
56-
await expect(AccountSettingsSectionPage(pageProps('unknown'))).rejects.toThrow('NEXT_NOT_FOUND')
57-
})
55+
it.each(['unknown', 'connected-accounts'])(
56+
'rejects unavailable sections: %s',
57+
async (section) => {
58+
await expect(AccountSettingsSectionPage(pageProps(section))).rejects.toThrow('NEXT_NOT_FOUND')
59+
}
60+
)
5861
})

apps/sim/app/credential-groups/enroll/[token]/page.test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,17 +148,17 @@ describe('focused Search enrollment', () => {
148148
expect(mocks.read).toHaveBeenCalledWith({ principal, input: {} })
149149
})
150150

151-
it('keeps account-settings reconnect focused and returns to account settings', async () => {
151+
it('keeps existing account reconnect links focused and returns to Sim', async () => {
152152
await render({ returnTo: 'accounts', optionId: 'site-two' })
153153
expect(oauthLinks().map((link) => link.getAttribute('href'))).toEqual([
154154
'/api/credential-groups/enroll/invitation/oauth/site-two?returnTo=accounts',
155155
])
156156
expect(document.querySelector('form')).toBeNull()
157157
expect(
158158
Array.from(document.querySelectorAll('a'))
159-
.find((link) => link.textContent === 'Your connected accounts')
159+
.find((link) => link.textContent === 'Open Sim')
160160
?.getAttribute('href')
161-
).toBe('/account/settings/connected-accounts')
161+
).toBe('/home')
162162
})
163163

164164
it('lets an account owner deliberately reconnect an active grant before reporting completion', async () => {
@@ -189,12 +189,12 @@ describe('focused Search enrollment', () => {
189189
})
190190
mocks.read.mockResolvedValue({ enrollment, canSearch })
191191
await render({ returnTo: 'search', optionId: 'site-two' })
192-
const label = canSearch ? 'Return to Search' : 'Your connected accounts'
192+
const label = canSearch ? 'Return to Search' : 'Open Sim'
193193
expect(
194194
Array.from(document.querySelectorAll('a'))
195195
.find((link) => link.textContent === label)
196196
?.getAttribute('href')
197-
).toBe(canSearch ? '/o/canonical-org/search' : '/account/settings/connected-accounts')
197+
).toBe(canSearch ? '/o/canonical-org/search' : '/home')
198198
}
199199
)
200200

apps/sim/app/credential-groups/enroll/[token]/page.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { Chip, ChipLink } from '@sim/emcn'
33
import type { Metadata } from 'next'
44
import { headers } from 'next/headers'
55
import { redirect } from 'next/navigation'
6-
import { getAccountSettingsHref } from '@/components/settings/navigation'
76
import { getSession } from '@/lib/auth'
87
import { asOrchestrationError } from '@/lib/core/orchestration/types'
98
import type { ResourceOwner } from '@/lib/core/resource-scope'
@@ -184,10 +183,8 @@ export default async function CredentialGroupEnrollmentPage({
184183
const canReturnToSearch =
185184
returnToSearch &&
186185
('canSearch' in enrollmentResult ? enrollmentResult.canSearch : !principal.organizationId)
187-
const returnHref = canReturnToSearch
188-
? searchReturnPath(principal)
189-
: getAccountSettingsHref('connected-accounts')
190-
const returnLabel = canReturnToSearch ? 'Return to Search' : 'Your connected accounts'
186+
const returnHref = canReturnToSearch ? searchReturnPath(principal) : APP_ENTRY_PATH
187+
const returnLabel = canReturnToSearch ? 'Return to Search' : 'Open Sim'
191188
if (!enrollment)
192189
return <UnavailableSearchConnection returnHref={returnHref} returnLabel={returnLabel} />
193190

apps/sim/app/o/[organizationId]/integrations/integrations.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ describe('organization integrations role and source paths', () => {
166166
expect(mocks.connect).toHaveBeenCalledExactlyOnceWith('search-index', 'member-source')
167167
})
168168

169-
it('keeps Slack return actions alongside the standard account and source actions', async () => {
169+
it('keeps Slack return actions alongside source management', async () => {
170170
mocks.context.mockReturnValue({
171171
organization: { id: scope.organizationId },
172172
viewer: { isAdmin: true },
@@ -177,7 +177,7 @@ describe('organization integrations role and source paths', () => {
177177
<OrganizationIntegrations slackOnboarding={{ token: 'slack-return', userId: 'member' }} />
178178
)
179179
)
180-
expect(document.body.textContent).toContain('Your accounts')
180+
expect(document.body.textContent).not.toContain('Your accounts')
181181
expect(document.body.textContent).toContain('Manage sources')
182182
expect(buttons('slack-return')).toHaveLength(1)
183183
})
@@ -353,7 +353,7 @@ describe('organization integrations role and source paths', () => {
353353
expect(
354354
document.querySelector('a[href="/o/organization-a/settings/integrations"]')
355355
).toHaveTextContent('Manage sources')
356-
expect(document.querySelector('a[href="/account/settings/connected-accounts"]')).not.toBeNull()
356+
expect(document.querySelector('a[href="/account/settings/connected-accounts"]')).toBeNull()
357357
expect(buttons('Add source')).toHaveLength(0)
358358
expect(buttons('Manage')).toHaveLength(0)
359359
expect(document.querySelector('[aria-label$="source actions"]')).toBeNull()

apps/sim/app/o/[organizationId]/integrations/integrations.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import { useMemo } from 'react'
44
import { Chip, ChipLink } from '@sim/emcn'
5-
import { getAccountSettingsHref } from '@/components/settings/navigation'
65
import type { ResourceScope } from '@/lib/core/resource-scope'
76
import { organizationRoutes } from '@/lib/navigation/paths'
87
import {
@@ -129,15 +128,16 @@ export function OrganizationIntegrations({ slackOnboarding }: OrganizationIntegr
129128
description='Connect your tools for Sim Search'
130129
tabs={TABS}
131130
action={
132-
<div className='flex flex-wrap items-center gap-2'>
133-
<ChipLink href={getAccountSettingsHref('connected-accounts')}>Your accounts</ChipLink>
134-
{viewer.isAdmin && (
135-
<ChipLink href={routes.settingsSection('integrations')}>Manage sources</ChipLink>
136-
)}
137-
{slackOnboarding && (
138-
<SlackSearchActions organizationId={organization.id} {...slackOnboarding} />
139-
)}
140-
</div>
131+
(viewer.isAdmin || slackOnboarding) && (
132+
<div className='flex flex-wrap items-center gap-2 empty:hidden'>
133+
{viewer.isAdmin && (
134+
<ChipLink href={routes.settingsSection('integrations')}>Manage sources</ChipLink>
135+
)}
136+
{slackOnboarding && (
137+
<SlackSearchActions organizationId={organization.id} {...slackOnboarding} />
138+
)}
139+
</div>
140+
)
141141
}
142142
>
143143
<div className={RESOURCE_LIST_STACK}>

apps/sim/components/settings/account-settings-renderer.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { usePostHog } from 'posthog-js/react'
66
import type { AccountSettingsSection } from '@/components/settings/navigation'
77
import { captureEvent } from '@/lib/posthog/client'
88
import { General } from '@/app/workspace/[workspaceId]/settings/components/general/general'
9-
import { PersonalOrganizationAccounts } from '@/ee/credential-groups/components/personal-organization-accounts'
109

1110
const Billing = dynamic(() =>
1211
import('@/app/workspace/[workspaceId]/settings/components/billing/billing').then(
@@ -40,7 +39,6 @@ export function AccountSettingsRenderer({ section }: AccountSettingsRendererProp
4039
captureEvent(posthog, 'settings_tab_viewed', { plane: 'account', section })
4140
}, [posthog, section])
4241

43-
if (section === 'connected-accounts') return <PersonalOrganizationAccounts />
4442
if (section === 'general') return <General />
4543
if (section === 'billing') return <Billing scope='account' />
4644
if (section === 'api-keys') return <ApiKeys scope='personal' />

apps/sim/components/settings/navigation.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ describe('settings navigation boundaries', () => {
128128
'general',
129129
'billing',
130130
'api-keys',
131-
'connected-accounts',
132131
'admin',
133132
'mothership',
134133
])

apps/sim/components/settings/navigation.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,7 @@ import { organizationRoutes } from '@/lib/navigation/paths'
3535

3636
export type SettingsPlane = 'account' | 'selfhost' | 'workspace'
3737

38-
export type AccountSettingsSection =
39-
| 'connected-accounts'
40-
| 'general'
41-
| 'billing'
42-
| 'api-keys'
43-
| 'admin'
44-
| 'mothership'
38+
export type AccountSettingsSection = 'general' | 'billing' | 'api-keys' | 'admin' | 'mothership'
4539

4640
/**
4741
* Settings a self-hoster needs from the managed service: their profile, what
@@ -539,14 +533,6 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[]
539533
order: 1,
540534
organizationSection: 'connected-accounts',
541535
},
542-
planes: {
543-
account: {
544-
id: 'connected-accounts',
545-
group: 'account',
546-
order: 3,
547-
description: 'Manage accounts you have contributed to organizations.',
548-
},
549-
},
550536
},
551537
{
552538
label: 'Custom tools',

apps/sim/ee/credential-groups/components/organization-account-people.test.tsx

Lines changed: 23 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,31 @@
22
import { act } from 'react'
33
import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
44
import { createRoot, type Root } from 'react-dom/client'
5-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5+
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
66

77
const mocks = vi.hoisted(() => ({
88
people: vi.fn(),
99
resend: vi.fn(),
1010
revoke: vi.fn(),
11-
disconnect: vi.fn(),
1211
reset: vi.fn(),
1312
resendState: { isPending: false, error: null as Error | null },
1413
revokeState: { isPending: false, error: null as Error | null },
1514
}))
1615
vi.mock('@/hooks/queries/organization-accounts', () => ({
1716
useOrganizationAccountPeople: mocks.people,
18-
usePersonalOrganizationAccounts: () => ({
19-
data: {
20-
pages: [
21-
{
22-
accounts: [
23-
{
24-
credentialId: 'credential-1',
25-
displayName: 'Personal Gmail',
26-
organizationName: 'Example organization',
27-
providerId: 'gmail',
28-
status: 'active',
29-
canReconnect: true,
30-
},
31-
],
32-
},
33-
],
34-
},
35-
}),
3617
useResendOrganizationAccountInvitation: () => ({ ...mocks.resendState, mutate: mocks.resend }),
3718
useRevokeOrganizationAccountEnrollment: () => ({
3819
...mocks.revokeState,
3920
mutate: mocks.revoke,
4021
reset: mocks.reset,
4122
}),
42-
useReconnectPersonalOrganizationAccount: () => ({}),
43-
useDisconnectPersonalOrganizationAccount: () => ({
44-
mutate: mocks.disconnect,
45-
reset: mocks.reset,
46-
}),
4723
}))
4824
vi.mock('@/ee/credential-groups/components/organization-account-invite-modal', () => ({
4925
OrganizationAccountInviteModal: () => null,
5026
}))
5127

5228
import { SettingsHeaderProvider, SettingsHeaderShell } from '@/components/settings/settings-header'
5329
import { OrganizationAccountPeople } from '@/ee/credential-groups/components/organization-account-people'
54-
import { PersonalOrganizationAccounts } from '@/ee/credential-groups/components/personal-organization-accounts'
5530

5631
let root: Root
5732
let container: HTMLDivElement
@@ -111,11 +86,6 @@ async function selectPersonAction(label: string) {
11186
return action
11287
}
11388

114-
async function openConfirmation(label: string) {
115-
if (label === 'Revoke') await selectPersonAction(label)
116-
else await act(async () => button(container, label).click())
117-
}
118-
11989
async function renderPeople(searchConnection?: { optionId: string; providerName: string }) {
12090
await act(async () =>
12191
root.render(
@@ -156,58 +126,28 @@ it('keeps the compact People rows and resends from the actions menu', async () =
156126
)
157127
})
158128

159-
const cases = [
160-
{
161-
label: 'Revoke',
162-
component: <OrganizationAccountPeople organizationId='organization-1' />,
163-
mutation: mocks.revoke,
164-
target: 'person@example.com',
165-
input: { organizationId: 'organization-1', enrollmentId: 'enrollment-1' },
166-
},
167-
{
168-
label: 'Disconnect',
169-
component: <PersonalOrganizationAccounts />,
170-
mutation: mocks.disconnect,
171-
target: 'Personal Gmail',
172-
input: 'credential-1',
173-
},
174-
] as const
175-
176-
describe.each(cases)(
177-
'$label organization account access',
178-
({ label, component, mutation, target, input }) => {
179-
it('requires confirmation, allows cancellation, and never submits from an unfocused Enter', async () => {
180-
await act(async () =>
181-
root.render(
182-
<NuqsTestingAdapter hasMemory>
183-
<SettingsHeaderProvider>
184-
<SettingsHeaderShell>{component}</SettingsHeaderShell>
185-
</SettingsHeaderProvider>
186-
</NuqsTestingAdapter>
187-
)
188-
)
189-
await openConfirmation(label)
190-
let dialog = document.querySelector('[role="dialog"]')
191-
expect(dialog?.textContent).toContain(target)
192-
expect(mutation).not.toHaveBeenCalled()
193-
await act(async () =>
194-
dialog?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))
195-
)
196-
expect(mutation).not.toHaveBeenCalled()
197-
if (!dialog) throw new Error('Missing confirmation dialog')
198-
await act(async () => button(dialog, 'Cancel').click())
199-
expect(mutation).not.toHaveBeenCalled()
200-
await openConfirmation(label)
201-
dialog = document.querySelector('[role="dialog"]')
202-
if (!dialog) throw new Error('Missing confirmation dialog')
203-
await act(async () => button(dialog, label).click())
204-
expect(mutation).toHaveBeenCalledExactlyOnceWith(
205-
input,
206-
expect.objectContaining({ onSuccess: expect.any(Function) })
207-
)
208-
})
209-
}
210-
)
129+
it('requires revoke confirmation, allows cancellation, and never submits from an unfocused Enter', async () => {
130+
await renderPeople()
131+
await selectPersonAction('Revoke')
132+
let dialog = document.querySelector('[role="dialog"]')
133+
expect(dialog?.textContent).toContain('person@example.com')
134+
expect(mocks.revoke).not.toHaveBeenCalled()
135+
await act(async () =>
136+
dialog?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))
137+
)
138+
expect(mocks.revoke).not.toHaveBeenCalled()
139+
if (!dialog) throw new Error('Missing confirmation dialog')
140+
await act(async () => button(dialog, 'Cancel').click())
141+
expect(mocks.revoke).not.toHaveBeenCalled()
142+
await selectPersonAction('Revoke')
143+
dialog = document.querySelector('[role="dialog"]')
144+
if (!dialog) throw new Error('Missing confirmation dialog')
145+
await act(async () => button(dialog, 'Revoke').click())
146+
expect(mocks.revoke).toHaveBeenCalledExactlyOnceWith(
147+
{ organizationId: 'organization-1', enrollmentId: 'enrollment-1' },
148+
expect.objectContaining({ onSuccess: expect.any(Function) })
149+
)
150+
})
211151

212152
it('restores the existing People URL search and requests server-filtered results', async () => {
213153
mocks.people.mockReturnValue({ data: { pages: [{ enrollments: [] }] }, hasNextPage: false })

0 commit comments

Comments
 (0)