Skip to content

Commit 652c451

Browse files
committed
Feed PlatformShell live data through its existing prop
platform/lib/data/shell.ts maps workspaces, chats and the profile onto ShellData with pure adapters over the existing hooks, plus the fields only real data has. Fixtures leave those fields out and render as before. Inbox and Sort workspaces hide where nothing backs them, tray history stays empty, and hooks' toasts reach the tray as notices, which may now carry no action.
1 parent 564039b commit 652c451

13 files changed

Lines changed: 624 additions & 64 deletions

File tree

‎apps/sim/hooks/queries/mothership-chats.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,8 @@ export function useMothershipChats(
350350
/** Private organization conversations use their own cache scope and current membership. */
351351
export function useOrganizationMothershipChats(
352352
organizationId: string,
353-
scope: MothershipChatScope = 'active'
353+
scope: MothershipChatScope = 'active',
354+
options?: { enabled?: boolean }
354355
) {
355356
return useQuery({
356357
queryKey: mothershipChatKeys.organizationList(organizationId, scope),
@@ -361,6 +362,7 @@ export function useOrganizationMothershipChats(
361362
})
362363
return data.data.map(mapChat)
363364
},
365+
enabled: options?.enabled ?? true,
364366
staleTime: MOTHERSHIP_CHAT_LIST_STALE_TIME,
365367
})
366368
}

‎apps/sim/platform/app/(shell)/_components/command-menu/command-menu.tsx‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { PLATFORM_DESTINATIONS } from '@/platform/app/(shell)/_components/destinations'
2-
import type { ShellData, ShellItem } from '@/platform/app/(shell)/_components/model'
2+
import type {
3+
ShellData,
4+
ShellDestination,
5+
ShellItem,
6+
} from '@/platform/app/(shell)/_components/model'
37
import { CommandPalette } from '@/platform/design-system/components/command-palette/command-palette'
48
import { OverlayDialog } from '@/platform/design-system/components/modal/overlay-dialog'
59

@@ -9,16 +13,24 @@ interface CommandMenuProps {
913
navigate: (href: string) => void
1014
chats: readonly ShellItem[]
1115
workspaces: ShellData['workspaces']
16+
destinations?: readonly ShellDestination[]
1217
}
1318

14-
export function CommandMenu({ open, onClose, navigate, chats, workspaces }: CommandMenuProps) {
19+
export function CommandMenu({
20+
open,
21+
onClose,
22+
navigate,
23+
chats,
24+
workspaces,
25+
destinations = PLATFORM_DESTINATIONS,
26+
}: CommandMenuProps) {
1527
return (
1628
<OverlayDialog label='Search' presentation='command' open={open} onClose={onClose}>
1729
<CommandPalette
1830
label='Search the platform'
1931
onSelect={navigate}
2032
groups={[
21-
{ items: PLATFORM_DESTINATIONS },
33+
{ items: destinations },
2234
{
2335
label: 'Workspaces',
2436
items: workspaces.map((item) => ({
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,39 @@
1+
import type { ShellDestination } from '@/platform/app/(shell)/_components/model'
2+
import { legacyRoutes, type PlatformOrganization } from '@/platform/lib/data/organization-model'
3+
14
export const PLATFORM_DESTINATIONS = [
25
{ id: 'home', label: 'Home', icon: 'I104', value: '/home' },
36
{ id: 'settings', label: 'Settings', icon: 'I116', value: '/settings' },
47
{ id: 'integrations', label: 'Integrations', icon: 'I070', value: '/integrations' },
58
] as const
9+
10+
/** The legacy navigation's gates and order. Search and Benchmark stay on the legacy surface. */
11+
export function resolveLiveDestinations(organization: PlatformOrganization): ShellDestination[] {
12+
const { access, navFlags } = organization
13+
const routes = legacyRoutes(organization)
14+
return [
15+
...(access.mothershipAvailable && access.canBuild
16+
? [{ id: 'home', label: 'Home', icon: 'I104' as const, value: '/home' }]
17+
: []),
18+
...(access.searchAccess.memberScoped
19+
? [{ id: 'search', label: 'Search', icon: 'I089' as const, value: routes.search }]
20+
: []),
21+
...(navFlags.people
22+
? [{ id: 'people', label: 'People', icon: 'I104' as const, value: '/people' }]
23+
: []),
24+
...(access.searchAccess.memberScoped
25+
? [
26+
{
27+
id: 'integrations',
28+
label: 'Integrations',
29+
icon: 'I070' as const,
30+
value: '/integrations',
31+
},
32+
]
33+
: []),
34+
...(navFlags.benchmark
35+
? [{ id: 'benchmark', label: 'Benchmark', icon: 'I046' as const, value: routes.benchmark }]
36+
: []),
37+
{ id: 'settings', label: 'Settings', icon: 'I116', value: '/settings' },
38+
]
39+
}

‎apps/sim/platform/app/(shell)/_components/model.ts‎

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { IconName } from '@/platform/design-system/catalog/icon-geometry'
2+
import type { PlatformOrganization } from '@/platform/lib/data/organization-model'
23

34
export type HomeMode = 'Build' | 'Search'
45
export type HomeWidget = 'Activity' | 'Email' | 'Calendar'
@@ -18,15 +19,73 @@ export interface InboxItem extends ShellItem {
1819
export interface ShellNotice extends ShellItem {
1920
description?: string
2021
icon: IconName
21-
action: string
22+
action?: string
2223
href?: string
2324
}
2425

26+
/** The optional fields are the ones only real data carries; fixtures leave them out. */
27+
export interface ShellWorkspace {
28+
id: string
29+
name: string
30+
logoUrl?: string | null
31+
permissions?: 'admin' | 'write' | 'read' | null
32+
isPinned?: boolean
33+
}
34+
35+
export interface ShellChat extends ShellItem {
36+
mode?: string
37+
/** Absolute when the chat opens on the legacy surface. */
38+
href?: string
39+
isActive?: boolean
40+
isUnread?: boolean
41+
isPinned?: boolean
42+
updatedAt?: string
43+
}
44+
45+
/** `value` is absolute when the destination stays on the legacy surface. */
46+
export interface ShellDestination {
47+
id: string
48+
label: string
49+
icon: IconName
50+
value: string
51+
}
52+
53+
export interface SettingsNavItem {
54+
key: string
55+
title: string
56+
icon: IconName
57+
/** Absolute when the section stays on the legacy surface. */
58+
href?: string
59+
}
60+
61+
export interface SettingsNavGroup {
62+
label: string
63+
items: readonly SettingsNavItem[]
64+
}
65+
66+
type ShellQueryStatus = 'pending' | 'error' | 'success'
67+
2568
export interface ShellData {
26-
organization: { name: string; memberCount: number }
27-
user: { name: string }
28-
workspaces: { id: string; name: string }[]
29-
chats: ShellItem[]
69+
organization: {
70+
name: string
71+
memberCount: number
72+
id?: string
73+
slug?: string
74+
logo?: string | null
75+
}
76+
user: { name: string; id?: string; email?: string; image?: string | null }
77+
workspaces: ShellWorkspace[]
78+
chats: ShellChat[]
3079
inbox: InboxItem[]
3180
notifications: ShellNotice[]
81+
/** Shell features with no backend in this data source. Hidden, because empty would misinform. */
82+
unbacked?: readonly ('inbox' | 'sortWorkspaces')[]
83+
/** So a list can tell loading and failure from empty. Absent means the data is simply there. */
84+
status?: { workspaces: ShellQueryStatus; chats: ShellQueryStatus; profile: ShellQueryStatus }
85+
workspaceCreation?: { canCreate: boolean; organizationId: string | null; reason: string | null }
86+
/** Viewer standing, access, deployment and flags. Absent outside a live session. */
87+
surface?: PlatformOrganization
88+
/** The viewer's gated navigation. Absent means the static destination lists. */
89+
destinations?: readonly ShellDestination[]
90+
settingsNavigation?: readonly SettingsNavGroup[]
3291
}

‎apps/sim/platform/app/(shell)/_components/notification-tray/notification-tray.tsx‎

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
'use client'
22

3-
import { createContext, type ReactNode, useContext, useState } from 'react'
3+
import { createContext, type ReactNode, useContext, useEffect, useState } from 'react'
4+
import { generateId } from '@sim/utils/id'
45
import type { ShellNotice } from '@/platform/app/(shell)/_components/model'
56
import styles from '@/platform/app/(shell)/_components/notification-tray/notification-tray.module.css'
67
import { ActivityBody } from '@/platform/design-system/components/activity-row/activity-body'
78
import { Button, IconButton } from '@/platform/design-system/components/button/button'
89
import { useFeedbackExpiry } from '@/platform/design-system/components/feedback/use-feedback-expiry'
910
import { Icon } from '@/platform/design-system/components/icon/icon'
1011
import { useScrollEdges } from '@/platform/design-system/components/scroll-area/use-scroll-edges'
12+
import { connectToastSink } from '@/platform/lib/data/toast'
1113

12-
type Notice = Omit<ShellNotice, 'href'> & { onAction: () => void }
14+
type Notice = Omit<ShellNotice, 'href'> & { onAction?: () => void }
1315
function useNoticeState() {
1416
const [edits, setEdits] = useState(() => new Map<string, Notice | null>())
1517
const [display, setDisplay] = useState<'hidden' | 'latest' | 'expanded' | 'minimized'>('hidden')
@@ -41,11 +43,37 @@ export function NotificationSession({ children }: { children: ReactNode }) {
4143
return <Notices.Provider value={state}>{children}</Notices.Provider>
4244
}
4345

46+
/** Delivers the toasts existing query hooks raise, as action-free notices unless they carry one. */
47+
function useToastNotices() {
48+
const { notify, dismiss } = usePlatformNotices()
49+
useEffect(
50+
() =>
51+
connectToastSink({
52+
show({ message, description, variant, action }) {
53+
const id = generateId()
54+
notify({
55+
id,
56+
title: message,
57+
description,
58+
icon: variant === 'error' || variant === 'warning' ? 'I047' : 'I106',
59+
action: action?.label,
60+
onAction: action?.onClick,
61+
})
62+
return id
63+
},
64+
dismiss,
65+
}),
66+
// notify and dismiss only write state, so the first render's pair stays correct.
67+
[]
68+
)
69+
}
70+
4471
interface NotificationTrayProps {
4572
history: readonly ShellNotice[]
4673
navigate: (href: string) => void
4774
}
4875
export function NotificationTray({ history, navigate }: NotificationTrayProps) {
76+
useToastNotices()
4977
const scrollEdges = useScrollEdges<HTMLDivElement>()
5078
const { edits, display, setDisplay, dismiss } = usePlatformNotices()
5179
const items = [
@@ -119,16 +147,18 @@ export function NotificationTray({ history, navigate }: NotificationTrayProps) {
119147
)}
120148
</ActivityBody>
121149
<div className={styles.actions}>
122-
<Button
123-
size='compact'
124-
onClick={() => {
125-
dismiss(item.id)
126-
if ('onAction' in item) item.onAction()
127-
else if (item.href) navigate(item.href)
128-
}}
129-
>
130-
{item.action}
131-
</Button>
150+
{item.action && (
151+
<Button
152+
size='compact'
153+
onClick={() => {
154+
dismiss(item.id)
155+
if ('onAction' in item) item.onAction?.()
156+
else if (item.href) navigate(item.href)
157+
}}
158+
>
159+
{item.action}
160+
</Button>
161+
)}
132162
<IconButton
133163
size='compact'
134164
label={`Dismiss ${item.title}`}

0 commit comments

Comments
 (0)