Skip to content

Commit 4d6c970

Browse files
committed
fix(settings): surface organization load failures
1 parent 8390f61 commit 4d6c970

2 files changed

Lines changed: 119 additions & 3 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { act, type ReactNode } from 'react'
5+
import { createRoot, type Root } from 'react-dom/client'
6+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
7+
8+
const { mockUseOrganization } = vi.hoisted(() => ({
9+
mockUseOrganization: vi.fn(),
10+
}))
11+
12+
vi.mock('@/lib/auth/auth-client', () => ({
13+
useSession: () => ({ data: { user: { email: 'viewer' } } }),
14+
}))
15+
16+
vi.mock('@/lib/billing/client/utils', () => ({
17+
getSubscriptionAccessState: () => ({
18+
hasUsableTeamAccess: false,
19+
hasUsableEnterpriseAccess: false,
20+
}),
21+
}))
22+
23+
vi.mock('@/lib/workspaces/organization', () => ({
24+
generateSlug: (value: string) => value.toLowerCase(),
25+
isAdminOrOwner: () => false,
26+
}))
27+
28+
vi.mock('@/app/workspace/[workspaceId]/components/invite-modal', () => ({
29+
InviteModal: () => null,
30+
}))
31+
32+
vi.mock('@/app/workspace/[workspaceId]/settings/components/settings-empty-state', () => ({
33+
SettingsEmptyState: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
34+
}))
35+
36+
vi.mock('@/app/workspace/[workspaceId]/settings/components/settings-panel', () => ({
37+
SettingsPanel: ({ children }: { children?: ReactNode }) => <section>{children}</section>,
38+
}))
39+
40+
vi.mock('@/app/workspace/[workspaceId]/settings/components/team-management/components', () => ({
41+
NoOrganizationView: () => <div>no-organization-view</div>,
42+
OrganizationMemberLists: () => null,
43+
RemoveMemberDialog: () => null,
44+
TeamSeatsOverview: () => null,
45+
TransferOwnershipDialog: () => null,
46+
}))
47+
48+
vi.mock('@/app/workspace/[workspaceId]/settings/components/use-settings-search', () => ({
49+
useSettingsSearch: () => ['', vi.fn()],
50+
}))
51+
52+
vi.mock('@/hooks/use-permission-config', () => ({
53+
usePermissionConfig: () => ({ isInvitationsDisabled: false }),
54+
}))
55+
56+
vi.mock('@/hooks/queries/subscription', () => ({
57+
useOpenBillingPortal: () => ({ mutate: vi.fn() }),
58+
useSubscriptionData: () => ({ data: undefined, isPending: false }),
59+
}))
60+
61+
vi.mock('@/hooks/queries/organization', () => ({
62+
useCreateOrganization: () => ({ error: null, isPending: false, mutateAsync: vi.fn() }),
63+
useMemberRemovalImpact: () => ({ data: [], isError: false, isFetching: false }),
64+
useOrganization: mockUseOrganization,
65+
useOrganizationBilling: () => ({ data: undefined, isLoading: false }),
66+
useOrganizationRoster: () => ({ data: undefined, isLoading: false }),
67+
useRemoveMember: () => ({ isPending: false, mutateAsync: vi.fn() }),
68+
useTransferOwnership: () => ({ isPending: false, mutateAsync: vi.fn() }),
69+
}))
70+
71+
import { TeamManagement } from '@/app/workspace/[workspaceId]/settings/components/team-management/team-management'
72+
73+
let container: HTMLDivElement
74+
let root: Root
75+
76+
beforeEach(() => {
77+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
78+
container = document.createElement('div')
79+
document.body.appendChild(container)
80+
root = createRoot(container)
81+
})
82+
83+
afterEach(() => {
84+
act(() => root.unmount())
85+
container.remove()
86+
vi.clearAllMocks()
87+
})
88+
89+
describe('TeamManagement organization errors', () => {
90+
it('shows the organization error instead of the missing-organization recovery view', () => {
91+
mockUseOrganization.mockReturnValue({
92+
data: undefined,
93+
error: new Error('Organization request failed'),
94+
isLoading: false,
95+
})
96+
97+
act(() => root.render(<TeamManagement organizationId='org-1' />))
98+
99+
expect(container.textContent).toContain('Organization request failed')
100+
expect(container.textContent).not.toContain('no-organization-view')
101+
})
102+
})

apps/sim/app/workspace/[workspaceId]/settings/components/team-management/team-management.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import { useCallback, useEffect, useState } from 'react'
44
import { Plus } from '@sim/emcn'
55
import { createLogger } from '@sim/logger'
6+
import { getErrorMessage } from '@sim/utils/errors'
67
import { useSession } from '@/lib/auth/auth-client'
78
import { getSubscriptionAccessState } from '@/lib/billing/client/utils'
89
import { getBaseUrl } from '@/lib/core/utils/urls'
910
import { generateSlug, isAdminOrOwner, type Member } from '@/lib/workspaces/organization'
1011
import { InviteModal } from '@/app/workspace/[workspaceId]/components/invite-modal'
12+
import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
1113
import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel'
1214
import {
1315
NoOrganizationView,
@@ -281,14 +283,22 @@ export function TeamManagement({
281283
)
282284
}, [organizationId, openBillingPortal])
283285

284-
const queryError = orgError
285-
const errorMessage = queryError instanceof Error ? queryError.message : null
286286
const displayOrganization = organization
287287

288288
if (isLoading && !displayOrganization) {
289289
return null
290290
}
291291

292+
if (orgError && !displayOrganization) {
293+
return (
294+
<SettingsPanel>
295+
<SettingsEmptyState tone='error'>
296+
{getErrorMessage(orgError, 'Failed to load organization')}
297+
</SettingsEmptyState>
298+
</SettingsPanel>
299+
)
300+
}
301+
292302
if (!displayOrganization && shouldLoadRecoverySubscription && isRecoverySubscriptionPending) {
293303
return null
294304
}
@@ -304,7 +314,11 @@ export function TeamManagement({
304314
onOrgNameChange={handleOrgNameChange}
305315
onCreateOrganization={handleCreateOrganization}
306316
isCreatingOrg={createOrgMutation.isPending}
307-
error={errorMessage}
317+
error={
318+
createOrgMutation.error
319+
? getErrorMessage(createOrgMutation.error, 'Failed to create organization')
320+
: null
321+
}
308322
createOrgDialogOpen={createOrgDialogOpen}
309323
setCreateOrgDialogOpen={setCreateOrgDialogOpen}
310324
/>

0 commit comments

Comments
 (0)