Skip to content

Commit cedf551

Browse files
committed
feat(org): organization surface UI, unified settings, and Sim Search setup
Home returns to the workspace chat's empty state, wired to the Assistant: the greeting, the framed composer with the shared animated placeholder, and a Get started list whose steps read completion from real state. Search is its own tab, a home-style greeting over a pill field that docks to the page head on submit with results scrolling beneath; a failed search shows one quiet line and Try again rather than the server's text. The Workspaces page is gone; the list lives in the sidebar above Chats as rail chips with the sidebar's See more paging and the collapsed rail flyout. The header dropdown is the organization card — mark, name, member count — over a Settings row; the workspace header loses its organizations list. Organization settings become one surface: Account (General, the organization's Subscription), Organization, Governance, and Sim Search, with account sections rendered by the account plane's own renderer. The settings Integrations section, admin-only, hosts every Sim Search source with Set up, Manage, and a confirm-gated approval toggle; the Integrations page shows every member the same list with only their own actions. The setup's OAuth detours return to the settings section through one helper. Sim Search in Slack is an outbound row in the shared settings sidebar. The org layout seeds the viewer's profile through a shared prefetch, so the footer paints hydrated and a page hydrating the same key no longer mismatches. Loading pages are removed; content lands as it arrives.
1 parent 715de0d commit cedf551

71 files changed

Lines changed: 1643 additions & 801 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/app/o/[organizationId]/chat/[chatId]/loading.tsx

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

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { Suspense } from 'react'
21
import type { Metadata } from 'next'
32
import { notFound } from 'next/navigation'
43
import { getSession } from '@/lib/auth'
54
import { getAccessibleCopilotChatAuth } from '@/lib/copilot/chat/lifecycle'
6-
import OrganizationChatLoading from '@/app/o/[organizationId]/chat/[chatId]/loading'
75
import { OrganizationHome } from '@/app/o/[organizationId]/home/organization-home'
86

97
export const metadata: Metadata = { title: 'Chat' }
@@ -20,9 +18,5 @@ export default async function OrganizationChatPage({
2018
principal: { kind: 'session', userId: session.user.id, sessionId: session.session.id },
2119
})
2220
if (!chat || chat.type !== 'mothership' || chat.organizationId !== organizationId) notFound()
23-
return (
24-
<Suspense fallback={<OrganizationChatLoading />}>
25-
<OrganizationHome userName={session.user.name ?? undefined} chatId={chatId} />
26-
</Suspense>
27-
)
21+
return <OrganizationHome userName={session.user.name ?? undefined} chatId={chatId} />
2822
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export {
22
OrganizationPage,
3-
OrganizationPageLoading,
43
type OrganizationPageTab,
4+
PAGE_COLUMN_CLASS,
55
} from '@/app/o/[organizationId]/components/organization-page/organization-page'
66
export { useOrganizationPageFilters } from '@/app/o/[organizationId]/components/organization-page/use-organization-page-filters'

apps/sim/app/o/[organizationId]/components/organization-page/organization-page.tsx

Lines changed: 10 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
useScrollEdges,
1313
} from '@sim/emcn'
1414
import { Search, X } from '@sim/emcn/icons'
15-
import { noop } from '@sim/utils/helpers'
1615
import { HEADER_ACTION_CLUSTER, PAGE_HEADER_BAR } from '@/components/page-header-bar'
1716
import { useOrganizationPageFilters } from '@/app/o/[organizationId]/components/organization-page/use-organization-page-filters'
1817
import {
@@ -21,7 +20,7 @@ import {
2120
} from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
2221

2322
/** The home surface's reading column, so every organization page shares its width. */
24-
const COLUMN_CLASS = 'mx-auto w-full max-w-chat px-6'
23+
export const PAGE_COLUMN_CLASS = 'mx-auto w-full max-w-chat px-6'
2524

2625
export interface OrganizationPageTab {
2726
id: string
@@ -33,6 +32,7 @@ interface OrganizationPageProps {
3332
description?: string
3433
/** Header tabs; the first is the default. Omit for a page with one view. */
3534
tabs?: readonly OrganizationPageTab[]
35+
/** The page's primary action, a chip. Omit for a page without one. */
3636
action?: ReactNode
3737
children?: ReactNode
3838
}
@@ -48,52 +48,20 @@ interface OrganizationPageProps {
4848
* skeleton standing in for it. Pass `tabs` only once they are known; the row
4949
* simply gains them.
5050
*/
51-
export function OrganizationPage(props: OrganizationPageProps) {
52-
const filters = useOrganizationPageFilters()
53-
return <OrganizationPageView {...props} filters={filters} />
54-
}
55-
56-
/** Uses the same page chrome during both navigation and a suspended URL read. */
57-
export function OrganizationPageLoading({
58-
title,
59-
description,
60-
}: Pick<OrganizationPageProps, 'title' | 'description'>) {
61-
return (
62-
<OrganizationPageView
63-
title={title}
64-
description={description}
65-
filters={{ tab: null, search: '', setTab: noop, setSearch: noop }}
66-
loading
67-
/>
68-
)
69-
}
70-
71-
interface OrganizationPageViewProps extends OrganizationPageProps {
72-
filters: {
73-
tab: string | null
74-
search: string
75-
setTab: (value: string | null) => void
76-
setSearch: (value: string) => void
77-
}
78-
loading?: boolean
79-
}
80-
81-
function OrganizationPageView({
51+
export function OrganizationPage({
8252
title,
8353
description,
8454
tabs,
8555
action,
8656
children,
87-
filters,
88-
loading = false,
89-
}: OrganizationPageViewProps) {
57+
}: OrganizationPageProps) {
9058
const scrollContainerRef = useRef<HTMLDivElement>(null)
9159
const scrollContentRef = useRef<HTMLDivElement>(null)
9260
const scrollEdges = useScrollEdges(scrollContainerRef, { contentRef: scrollContentRef })
9361
const tabsRef = useRef<HTMLDivElement>(null)
9462
const tabEdges = useScrollEdges(tabsRef, { axis: 'x' })
9563

96-
const { tab, search, setTab, setSearch } = filters
64+
const { tab, search, setTab, setSearch } = useOrganizationPageFilters()
9765
const defaultTab = tabs?.[0]?.id
9866
const activeTab = tab ?? defaultTab
9967

@@ -117,7 +85,9 @@ function OrganizationPageView({
11785
<div className={HEADER_ACTION_CLUSTER} />
11886
</div>
11987

120-
<div className={cn(COLUMN_CLASS, SIDEBAR_DIVIDER_PAD_ABOVE_CLASS, 'flex shrink-0 flex-col')}>
88+
<div
89+
className={cn(PAGE_COLUMN_CLASS, SIDEBAR_DIVIDER_PAD_ABOVE_CLASS, 'flex shrink-0 flex-col')}
90+
>
12191
<div className='flex flex-col gap-1 pt-8'>
12292
<h1 className='text-[var(--text-primary)] text-lg'>{title}</h1>
12393
{description && <p className='text-[var(--text-muted)] text-small'>{description}</p>}
@@ -178,12 +148,7 @@ function OrganizationPageView({
178148
}
179149
/>
180150
) : (
181-
<Chip
182-
disabled={loading}
183-
leftIcon={Search}
184-
aria-label='Search'
185-
onClick={() => setSearchOpened(true)}
186-
/>
151+
<Chip leftIcon={Search} aria-label='Search' onClick={() => setSearchOpened(true)} />
187152
)}
188153
{action}
189154
</div>
@@ -200,7 +165,7 @@ function OrganizationPageView({
200165
)}
201166
{...scrollFadeAttributes(scrollEdges)}
202167
>
203-
<div ref={scrollContentRef} className={COLUMN_CLASS}>
168+
<div ref={scrollContentRef} className={PAGE_COLUMN_CLASS}>
204169
{children}
205170
</div>
206171
</div>

apps/sim/app/o/[organizationId]/components/organization-sidebar/components/chats-section/chats-section.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import {
99
CollapsedSidebarMenu,
1010
SidebarSection,
1111
} from '@/app/workspace/[workspaceId]/w/components/sidebar/components'
12-
import { SIDEBAR_ITEM_GAP_CLASS } from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
12+
import {
13+
SIDEBAR_ITEM_GAP_CLASS,
14+
SIDEBAR_SECTION_GAP_CLASS,
15+
} from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
1316
import { useHoverMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
1417

1518
/** Stands in for a chip row while the list loads, so it carries no margin either. */
@@ -96,11 +99,10 @@ interface ChatsSectionProps {
9699
}
97100

98101
/**
99-
* The organization's chats: the first section of the scroll region, so it carries no
100-
* section gap — the divider padding above it is the whole distance, exactly as the
101-
* workspace sidebar spaces its own Chats. Expanded, a collapsible list of every chat —
102-
* no paging, the scroll region carries the length; collapsed, a hover flyout off the
103-
* rail glyph.
102+
* The organization's chats, the section beneath Workspaces, spaced from it by the
103+
* section gap exactly as the workspace sidebar spaces its own sections. Expanded, a
104+
* collapsible list of every chat — no paging, the scroll region carries the length;
105+
* collapsed, a hover flyout off the rail glyph.
104106
*/
105107
export function ChatsSection({
106108
chats,
@@ -114,7 +116,11 @@ export function ChatsSection({
114116
const hover = useHoverMenu()
115117

116118
return (
117-
<SidebarSection title='Chats' railCollapsed={isCollapsed} className='chats-section shrink-0'>
119+
<SidebarSection
120+
title='Chats'
121+
railCollapsed={isCollapsed}
122+
className={cn(SIDEBAR_SECTION_GAP_CLASS, 'chats-section shrink-0')}
123+
>
118124
{isCollapsed ? (
119125
<div className='px-2'>
120126
<CollapsedSidebarMenu

apps/sim/app/o/[organizationId]/components/organization-sidebar/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export { ChatsSection } from './chats-section'
22
export { OrganizationFooter } from './organization-footer'
33
export { OrganizationHeader } from './organization-header'
44
export { WorkspacesRailFlyout } from './workspaces-rail-flyout'
5+
export { WorkspacesSection } from './workspaces-section'

apps/sim/app/o/[organizationId]/components/organization-sidebar/components/organization-footer/organization-footer.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ import {
1919
import { BookOpen, Download, HelpCircle, Settings } from '@sim/emcn/icons'
2020
import Link from 'next/link'
2121
import { SlackIcon } from '@/components/icons'
22-
import { getAccountSettingsHref } from '@/components/settings/navigation'
2322
import { getDesktopUpdates } from '@/lib/desktop'
23+
import { organizationRoutes } from '@/lib/navigation/paths'
2424
import { getUserColor } from '@/lib/workspaces/colors'
25+
import { useOrganizationContext } from '@/app/o/[organizationId]/providers/organization-provider'
2526
import { SidebarTooltip } from '@/app/workspace/[workspaceId]/w/components/sidebar/components'
2627
import {
2728
SIDEBAR_ITEM_GAP_CLASS,
@@ -89,6 +90,7 @@ export function OrganizationFooter({
8990
onOpenDocs,
9091
onJoinSlack,
9192
}: OrganizationFooterProps) {
93+
const { organization } = useOrganizationContext()
9294
const { data: profile } = useUserProfile()
9395
const updateState = useDesktopUpdateState()
9496

@@ -164,7 +166,7 @@ export function OrganizationFooter({
164166
</SidebarTooltip>
165167
<DropdownMenuContent align='start' side='top' sideOffset={4}>
166168
<DropdownMenuItem asChild>
167-
<Link href={getAccountSettingsHref('general')}>
169+
<Link href={organizationRoutes(organization.id).settingsSection('general')}>
168170
<Settings className='size-[14px]' />
169171
<DropdownMenuItemLabel label='Settings' />
170172
</Link>

apps/sim/app/o/[organizationId]/components/organization-sidebar/components/organization-header/organization-header.tsx

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@ import {
88
DropdownMenuItem,
99
DropdownMenuTrigger,
1010
} from '@sim/emcn'
11-
import { Building, PanelLeft, Settings } from '@sim/emcn/icons'
11+
import { PanelLeft, Settings } from '@sim/emcn/icons'
1212
import { IdentityTile } from '@/components/identity-tile/identity-tile'
13-
import { OrganizationMenuItems } from '@/components/organization-menu-items/organization-menu-items'
1413
import { getOrganizationSettingsHref } from '@/components/settings/navigation'
1514
import { SettingsGuardedLink } from '@/components/settings/settings-guarded-link'
16-
import { WORKSPACES_PATH } from '@/lib/navigation/paths'
1715
import type { OrganizationSurfaceOrganization } from '@/lib/organizations/surface'
1816
import { SIDEBAR_RAIL_CHIP_CLASS } from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
1917

@@ -30,7 +28,8 @@ interface OrganizationHeaderProps {
3028

3129
/**
3230
* The top-left organization chip. Expanded, it names the organization and opens
33-
* the organization menu; collapsed, it becomes the rail's expand control, swapping
31+
* its card — the mark at tile size, the name, how many people belong, and the way
32+
* into its settings; collapsed, it becomes the rail's expand control, swapping
3433
* the mark for a panel glyph on hover exactly as the workspace header does. The
3534
* mark is the organization's uploaded logo or its initial on the neutral tile.
3635
*/
@@ -39,6 +38,8 @@ export function OrganizationHeader({
3938
isCollapsed,
4039
onExpandSidebar,
4140
}: OrganizationHeaderProps) {
41+
const initial = getOrganizationInitial(organization.name)
42+
4243
if (isCollapsed) {
4344
return (
4445
<div className='min-w-0 flex-1'>
@@ -50,7 +51,7 @@ export function OrganizationHeader({
5051
leftAdornment={
5152
<div className='relative flex size-[16px] shrink-0 items-center justify-center'>
5253
<IdentityTile
53-
initial={getOrganizationInitial(organization.name)}
54+
initial={initial}
5455
logoUrl={organization.logo}
5556
className='group-hover:invisible'
5657
/>
@@ -65,19 +66,16 @@ export function OrganizationHeader({
6566
)
6667
}
6768

69+
const { memberCount } = organization
70+
6871
return (
6972
<div className='min-w-0 flex-1'>
7073
<DropdownMenu>
7174
<DropdownMenuTrigger asChild>
7275
<Chip
7376
aria-label='Organization menu'
7477
className='min-w-0 max-w-full'
75-
leftAdornment={
76-
<IdentityTile
77-
initial={getOrganizationInitial(organization.name)}
78-
logoUrl={organization.logo}
79-
/>
80-
}
78+
leftAdornment={<IdentityTile initial={initial} logoUrl={organization.logo} />}
8179
rightAdornment={<ChipChevronDown />}
8280
>
8381
{organization.name}
@@ -89,17 +87,27 @@ export function OrganizationHeader({
8987
sideOffset={8}
9088
className='w-64 max-w-[calc(100vw-24px)]'
9189
>
92-
<OrganizationMenuItems currentOrganizationId={organization.id} />
90+
{/* The item rows' `px-2` and the rail chips' icon-to-label gap, so the card sits on the menu's own grid. */}
91+
<div className='flex items-center gap-2 px-2 py-1.5'>
92+
<IdentityTile
93+
size='lg'
94+
initial={initial}
95+
logoUrl={organization.logo}
96+
alt={organization.name}
97+
/>
98+
<div className='flex min-w-0 flex-col'>
99+
<span className='truncate text-[var(--text-primary)] text-sm'>
100+
{organization.name}
101+
</span>
102+
<span className='text-[var(--text-muted)] text-caption'>
103+
{memberCount} {memberCount === 1 ? 'member' : 'members'}
104+
</span>
105+
</div>
106+
</div>
93107
<DropdownMenuItem asChild>
94108
<SettingsGuardedLink href={getOrganizationSettingsHref(organization.id, 'members')}>
95109
<Settings className='size-[14px]' />
96-
Organization settings
97-
</SettingsGuardedLink>
98-
</DropdownMenuItem>
99-
<DropdownMenuItem asChild>
100-
<SettingsGuardedLink href={WORKSPACES_PATH}>
101-
<Building className='size-[14px]' />
102-
Switch workspace
110+
Settings
103111
</SettingsGuardedLink>
104112
</DropdownMenuItem>
105113
</DropdownMenuContent>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { WorkspacesSection } from './workspaces-section'

0 commit comments

Comments
 (0)