feat(web): surface Automations in the sidebar under Inbox

Adds an "Automations" nav entry rendered explicitly between Inbox and
(on mobile) Documents, mirroring how those two are pulled out of the
nav list and rendered above the chat sections. The icon is Workflow
to match settings/RBAC labelling.

LayoutDataProvider:
- Adds the entry to navItems pointing at /dashboard/[id]/automations.
- Marks isActive via pathname so the row highlights on the route.
- Tags /automations as a workspace-panel page so it renders in the
  centered settings-style viewport (same chrome as Team / settings).

Sidebar:
- Pulls out automationsItem alongside inboxItem and documentsItem.
- Renders it between them.
- Excludes its URL from footerNavItems so it doesn't double-render.

Page-level RBAC still gates the actual view; the sidebar entry is
always visible (consistent with Inbox/Documents which are also not
gated at the nav layer).

Anonymous (FreeLayoutDataProvider) intentionally not touched —
automations is an authenticated feature.
This commit is contained in:
CREDO23 2026-05-28 01:11:20 +02:00
parent fe28833ad4
commit 7bc52dcdc0
2 changed files with 50 additions and 12 deletions

View file

@ -2,7 +2,7 @@
import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { useAtom, useAtomValue, useSetAtom } from "jotai";
import { AlertTriangle, Inbox, LibraryBig } from "lucide-react"; import { AlertTriangle, Inbox, LibraryBig, Workflow } from "lucide-react";
import { useParams, usePathname, useRouter } from "next/navigation"; import { useParams, usePathname, useRouter } from "next/navigation";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
@ -335,9 +335,10 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
}, [threadsData, searchSpaceId]); }, [threadsData, searchSpaceId]);
// Navigation items // Navigation items
// Inbox is rendered explicitly below "New chat" in the sidebar (it is also // Inbox, Automations, and Documents are rendered explicitly below "New chat"
// surfaced in the icon rail's collapsed mode via this list). Announcements // in the sidebar (also surfaced in the icon rail's collapsed mode via this
// has been moved to the avatar dropdown and is no longer a nav item. // list). Announcements has been moved to the avatar dropdown.
const isAutomationsActive = pathname?.includes("/automations") === true;
const navItems: NavItem[] = useMemo( const navItems: NavItem[] = useMemo(
() => () =>
( (
@ -349,6 +350,12 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
isActive: isInboxSidebarOpen, isActive: isInboxSidebarOpen,
badge: totalUnreadCount > 0 ? formatInboxCount(totalUnreadCount) : undefined, badge: totalUnreadCount > 0 ? formatInboxCount(totalUnreadCount) : undefined,
}, },
{
title: "Automations",
url: `/dashboard/${searchSpaceId}/automations`,
icon: Workflow,
isActive: isAutomationsActive,
},
isMobile isMobile
? { ? {
title: "Documents", title: "Documents",
@ -359,7 +366,14 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
: null, : null,
] as (NavItem | null)[] ] as (NavItem | null)[]
).filter((item): item is NavItem => item !== null), ).filter((item): item is NavItem => item !== null),
[isMobile, isInboxSidebarOpen, isDocumentsSidebarOpen, totalUnreadCount] [
isMobile,
isInboxSidebarOpen,
isDocumentsSidebarOpen,
totalUnreadCount,
searchSpaceId,
isAutomationsActive,
]
); );
// Handlers // Handlers
@ -660,12 +674,14 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
const isUserSettingsPage = pathname?.includes("/user-settings") === true; const isUserSettingsPage = pathname?.includes("/user-settings") === true;
const isSearchSpaceSettingsPage = pathname?.includes("/search-space-settings") === true; const isSearchSpaceSettingsPage = pathname?.includes("/search-space-settings") === true;
const isTeamPage = pathname?.endsWith("/team") === true; const isTeamPage = pathname?.endsWith("/team") === true;
const isAutomationsPage = pathname?.includes("/automations") === true;
const useWorkspacePanel = const useWorkspacePanel =
pathname?.endsWith("/buy-more") === true || pathname?.endsWith("/buy-more") === true ||
pathname?.endsWith("/more-pages") === true || pathname?.endsWith("/more-pages") === true ||
isUserSettingsPage || isUserSettingsPage ||
isSearchSpaceSettingsPage || isSearchSpaceSettingsPage ||
isTeamPage; isTeamPage ||
isAutomationsPage;
return ( return (
<> <>
@ -705,12 +721,14 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
isChatPage={isChatPage} isChatPage={isChatPage}
useWorkspacePanel={useWorkspacePanel} useWorkspacePanel={useWorkspacePanel}
workspacePanelViewportClassName={ workspacePanelViewportClassName={
isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage || isAutomationsPage
? "items-start justify-center px-6 py-8 md:px-10 md:py-10" ? "items-start justify-center px-6 py-8 md:px-10 md:py-10"
: undefined : undefined
} }
workspacePanelContentClassName={ workspacePanelContentClassName={
isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage ? "max-w-5xl" : undefined isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage || isAutomationsPage
? "max-w-5xl"
: undefined
} }
isLoadingChats={isLoadingThreads} isLoadingChats={isLoadingThreads}
activeSlideoutPanel={activeSlideoutPanel} activeSlideoutPanel={activeSlideoutPanel}

View file

@ -140,16 +140,26 @@ export function Sidebar({
const t = useTranslations("sidebar"); const t = useTranslations("sidebar");
const [openDropdownChatId, setOpenDropdownChatId] = useState<number | null>(null); const [openDropdownChatId, setOpenDropdownChatId] = useState<number | null>(null);
// Inbox and Documents are rendered explicitly right below New Chat. Pull // Inbox, Automations, and Documents are rendered explicitly right below
// them out of the nav items list so they don't also appear in the bottom // New Chat. Pull them out of the nav items list so they don't also appear
// NavSection. Documents is only present in navItems on mobile. // in the bottom NavSection. Documents is only present in navItems on
// mobile; Automations is identified by URL suffix so the same code path
// works across search spaces.
const inboxItem = useMemo(() => navItems.find((item) => item.url === "#inbox"), [navItems]); const inboxItem = useMemo(() => navItems.find((item) => item.url === "#inbox"), [navItems]);
const automationsItem = useMemo(
() => navItems.find((item) => item.url.endsWith("/automations")),
[navItems]
);
const documentsItem = useMemo( const documentsItem = useMemo(
() => navItems.find((item) => item.url === "#documents"), () => navItems.find((item) => item.url === "#documents"),
[navItems] [navItems]
); );
const footerNavItems = useMemo( const footerNavItems = useMemo(
() => navItems.filter((item) => item.url !== "#inbox" && item.url !== "#documents"), () =>
navItems.filter(
(item) =>
item.url !== "#inbox" && item.url !== "#documents" && !item.url.endsWith("/automations")
),
[navItems] [navItems]
); );
@ -227,6 +237,16 @@ export function Sidebar({
} }
/> />
)} )}
{automationsItem && (
<SidebarButton
icon={automationsItem.icon}
label={automationsItem.title}
onClick={() => onNavItemClick?.(automationsItem)}
isCollapsed={isCollapsed}
isActive={automationsItem.isActive}
tooltipContent={isCollapsed ? automationsItem.title : undefined}
/>
)}
{documentsItem && ( {documentsItem && (
<SidebarButton <SidebarButton
icon={documentsItem.icon} icon={documentsItem.icon}