suna/frontend/src/components/sidebar/sidebar-left.tsx

252 lines
8.7 KiB
TypeScript

'use client';
import * as React from 'react';
import Link from 'next/link';
import { Bot, Menu, Store, Plus, Zap, Plug, ChevronRight, Loader2 } from 'lucide-react';
import { NavAgents } from '@/components/sidebar/nav-agents';
import { NavUserWithTeams } from '@/components/sidebar/nav-user-with-teams';
import { KortixLogo } from '@/components/sidebar/kortix-logo';
import { CTACard } from '@/components/sidebar/cta';
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarRail,
SidebarTrigger,
useSidebar,
} from '@/components/ui/sidebar';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import { NewAgentDialog } from '@/components/agents/new-agent-dialog';
import { useEffect, useState } from 'react';
import { createClient } from '@/lib/supabase/client';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { useIsMobile } from '@/hooks/use-mobile';
import { cn } from '@/lib/utils';
import { usePathname, useSearchParams } from 'next/navigation';
import { useFeatureFlags } from '@/lib/feature-flags';
import posthog from 'posthog-js';
export function SidebarLeft({
...props
}: React.ComponentProps<typeof Sidebar>) {
const { state, setOpen, setOpenMobile } = useSidebar();
const isMobile = useIsMobile();
const [user, setUser] = useState<{
name: string;
email: string;
avatar: string;
}>({
name: 'Loading...',
email: 'loading@example.com',
avatar: '',
});
const pathname = usePathname();
const searchParams = useSearchParams();
const { flags, loading: flagsLoading } = useFeatureFlags(['custom_agents', 'agent_marketplace']);
const customAgentsEnabled = flags.custom_agents;
const marketplaceEnabled = flags.agent_marketplace;
const [showNewAgentDialog, setShowNewAgentDialog] = useState(false);
useEffect(() => {
const fetchUserData = async () => {
const supabase = createClient();
const { data } = await supabase.auth.getUser();
if (data.user) {
setUser({
name:
data.user.user_metadata?.name ||
data.user.email?.split('@')[0] ||
'User',
email: data.user.email || '',
avatar: data.user.user_metadata?.avatar_url || '',
});
}
};
fetchUserData();
}, []);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if ((event.metaKey || event.ctrlKey) && event.key === 'b') {
event.preventDefault();
setOpen(!state.startsWith('expanded'));
window.dispatchEvent(
new CustomEvent('sidebar-left-toggled', {
detail: { expanded: !state.startsWith('expanded') },
}),
);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [state, setOpen]);
return (
<Sidebar
collapsible="icon"
className="border-r-0 bg-background/95 backdrop-blur-sm [&::-webkit-scrollbar]:hidden [-ms-overflow-style:'none'] [scrollbar-width:'none']"
{...props}
>
<SidebarHeader className="px-2 py-2">
<div className="flex h-[40px] items-center px-1 relative">
<Link href="/dashboard">
<KortixLogo />
</Link>
{state !== 'collapsed' && (
<div className="ml-2 transition-all duration-200 ease-in-out whitespace-nowrap">
</div>
)}
<div className="ml-auto flex items-center gap-2">
{state !== 'collapsed' && (
<Tooltip>
<TooltipTrigger asChild>
<SidebarTrigger className="h-8 w-8" />
</TooltipTrigger>
<TooltipContent>Toggle sidebar (CMD+B)</TooltipContent>
</Tooltip>
)}
{isMobile && (
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => setOpenMobile(true)}
className="h-8 w-8 flex items-center justify-center rounded-md hover:bg-accent"
>
<Menu className="h-4 w-4" />
</button>
</TooltipTrigger>
<TooltipContent>Open menu</TooltipContent>
</Tooltip>
)}
</div>
</div>
</SidebarHeader>
<SidebarContent className="[&::-webkit-scrollbar]:hidden [-ms-overflow-style:'none'] [scrollbar-width:'none']">
<SidebarGroup>
<Link href="/dashboard">
<SidebarMenuButton className={cn({
'bg-accent text-accent-foreground font-medium': pathname === '/dashboard',
})} onClick={() => posthog.capture('new_task_clicked')}>
<Plus className="h-4 w-4 mr-1" />
<span className="flex items-center justify-between w-full">
New Task
</span>
</SidebarMenuButton>
</Link>
{!flagsLoading && customAgentsEnabled && (
<SidebarMenu>
<Collapsible
defaultOpen={pathname?.includes('/agents')}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton
tooltip="Agents"
>
<Bot className="h-4 w-4 mr-1" />
<span>Agents</span>
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem>
<SidebarMenuSubButton className={cn('pl-3', {
'bg-accent text-accent-foreground font-medium': pathname === '/agents' && searchParams.get('tab') === 'marketplace',
})} asChild>
<Link href="/agents?tab=marketplace">
<span>Explore</span>
</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
<SidebarMenuSubItem>
<SidebarMenuSubButton className={cn('pl-3', {
'bg-accent text-accent-foreground font-medium': pathname === '/agents' && (searchParams.get('tab') === 'my-agents' || searchParams.get('tab') === null),
})} asChild>
<Link href="/agents?tab=my-agents">
<span>My Agents</span>
</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
<SidebarMenuSubItem>
<SidebarMenuSubButton
onClick={() => setShowNewAgentDialog(true)}
className="cursor-pointer pl-3"
>
<span>New Agent</span>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
</SidebarMenu>
)}
{!flagsLoading && customAgentsEnabled && (
<Link href="/settings/credentials">
<SidebarMenuButton className={cn({
'bg-accent text-accent-foreground font-medium': pathname === '/settings/credentials',
})}>
<Plug className="h-4 w-4 mr-1" />
<span className="flex items-center justify-between w-full">
Integrations
</span>
</SidebarMenuButton>
</Link>
)}
</SidebarGroup>
<NavAgents />
</SidebarContent>
{state !== 'collapsed' && (
<div className="px-3 py-2">
<CTACard />
</div>
)}
<SidebarFooter>
{state === 'collapsed' && (
<div className="mt-2 flex justify-center">
<Tooltip>
<TooltipTrigger asChild>
<SidebarTrigger className="h-8 w-8" />
</TooltipTrigger>
<TooltipContent>Expand sidebar (CMD+B)</TooltipContent>
</Tooltip>
</div>
)}
<NavUserWithTeams user={user} />
</SidebarFooter>
<SidebarRail />
<NewAgentDialog
open={showNewAgentDialog}
onOpenChange={setShowNewAgentDialog}
/>
</Sidebar>
);
}