Skip to content

Commit deaa7a2

Browse files
committed
fix(organization): route chat URLs the same way as organization Home
The chat page gated on (mothershipAvailable || memberScoped) while OrganizationHome renders nothing without copilot.use, or without Build and Search. A viewer with copilot.use but neither Build nor Search, and a Search-only viewer opening an assistant chat, got a blank page. Apply Home's two redirects (Search when copilot.use is denied, workspace settings when neither Build nor Search is allowed) before loading the chat, through one getOrganizationHomeRedirect shared by both pages so they cannot drift again; the later copilot.use redirect becomes unreachable and is removed.
1 parent 86aad3f commit deaa7a2

4 files changed

Lines changed: 44 additions & 8 deletions

File tree

‎apps/sim/app/o/[organizationId]/chat/[chatId]/page.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getAccessibleCopilotChatAuth } from '@/lib/mothership/chat/lifecycle'
66
import { WORKSPACE_SETTINGS_PATH } from '@/lib/navigation/paths'
77
import { getOrganizationSurfaceContext } from '@/lib/organizations/surface'
88
import OrganizationChatLoading from '@/app/o/[organizationId]/chat/[chatId]/loading'
9+
import { getOrganizationHomeRedirect } from '@/app/o/[organizationId]/home/home-redirect'
910
import { OrganizationHome } from '@/app/o/[organizationId]/home/organization-home'
1011

1112
export const metadata: Metadata = { title: 'Chat' }
@@ -20,8 +21,8 @@ export default async function OrganizationChatPage({
2021
if (!session?.user?.id) notFound()
2122
const context = await getOrganizationSurfaceContext(organizationId, session.user.id)
2223
if (!context) notFound()
23-
if (!context.mothershipAvailable && !context.searchAccess.memberScoped)
24-
redirect(WORKSPACE_SETTINGS_PATH)
24+
const homeRedirect = getOrganizationHomeRedirect(context, organizationId)
25+
if (homeRedirect) redirect(homeRedirect)
2526
const chat = await getAccessibleCopilotChatAuth(chatId, session.user.id, {
2627
principal: { kind: 'session', userId: session.user.id, sessionId: session.session.id },
2728
})
@@ -38,7 +39,6 @@ export default async function OrganizationChatPage({
3839
</Suspense>
3940
)
4041
}
41-
if (!context.mothershipAvailable) redirect(WORKSPACE_SETTINGS_PATH)
4242
return (
4343
<Suspense fallback={<OrganizationChatLoading />}>
4444
<OrganizationHome
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { organizationRoutes, WORKSPACE_SETTINGS_PATH } from '@/lib/navigation/paths'
2+
import type { OrganizationSurfaceContext } from '@/lib/organizations/surface'
3+
4+
/**
5+
* Where organization Home and its chat URLs send a viewer Home cannot serve, or `null` when
6+
* Home renders: Search when Chat is off but member Search is on, otherwise workspace settings
7+
* unless the viewer can chat and build or search as a member.
8+
*/
9+
export function getOrganizationHomeRedirect(
10+
context: Pick<OrganizationSurfaceContext, 'mothershipAvailable' | 'canBuild' | 'searchAccess'>,
11+
organizationId: string
12+
): string | null {
13+
if (!context.mothershipAvailable && context.searchAccess.memberScoped) {
14+
return organizationRoutes(organizationId).search
15+
}
16+
if (!(context.mothershipAvailable && context.canBuild) && !context.searchAccess.memberScoped) {
17+
return WORKSPACE_SETTINGS_PATH
18+
}
19+
return null
20+
}

‎apps/sim/app/o/[organizationId]/home/page.test.tsx‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,24 @@ describe('organization Search page gates', () => {
152152
)
153153
expect(mocks.chat).not.toHaveBeenCalled()
154154
})
155+
156+
it('routes chat URLs exactly where Home routes the same viewer, before loading the chat', async () => {
157+
mocks.context.mockResolvedValue({
158+
mothershipAvailable: true,
159+
canBuild: false,
160+
searchAccess: { memberScoped: false },
161+
})
162+
await expect(OrganizationChatPage({ params })).rejects.toThrow(
163+
'redirect:/workspace?redirect=settings'
164+
)
165+
mocks.context.mockResolvedValue({
166+
mothershipAvailable: false,
167+
canBuild: false,
168+
searchAccess: { memberScoped: true },
169+
})
170+
await expect(OrganizationChatPage({ params })).rejects.toThrow('redirect:/o/org-1/search')
171+
expect(mocks.chat).not.toHaveBeenCalled()
172+
})
155173
})
156174

157175
it('renders standalone Search independently of assistant availability', async () => {

‎apps/sim/app/o/[organizationId]/home/page.tsx‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { Suspense } from 'react'
22
import type { Metadata } from 'next'
33
import { notFound, redirect } from 'next/navigation'
44
import { getSession } from '@/lib/auth'
5-
import { organizationRoutes, WORKSPACE_SETTINGS_PATH } from '@/lib/navigation/paths'
65
import { getOrganizationSurfaceContext } from '@/lib/organizations/surface'
6+
import { getOrganizationHomeRedirect } from '@/app/o/[organizationId]/home/home-redirect'
77
import { OrganizationHome } from '@/app/o/[organizationId]/home/organization-home'
88
import { HomeFallback } from '@/app/workspace/[workspaceId]/home/home-fallback'
99

@@ -21,10 +21,8 @@ export default async function OrganizationHomePage({
2121
if (!session?.user?.id) notFound()
2222
const context = await getOrganizationSurfaceContext(organizationId, session.user.id)
2323
if (!context) notFound()
24-
if (!context.mothershipAvailable && context.searchAccess.memberScoped)
25-
redirect(organizationRoutes(organizationId).search)
26-
if (!(context.mothershipAvailable && context.canBuild) && !context.searchAccess.memberScoped)
27-
redirect(WORKSPACE_SETTINGS_PATH)
24+
const homeRedirect = getOrganizationHomeRedirect(context, organizationId)
25+
if (homeRedirect) redirect(homeRedirect)
2826

2927
return (
3028
<Suspense fallback={<HomeFallback />}>

0 commit comments

Comments
 (0)