mirror of https://github.com/kortix-ai/suna.git
wip
This commit is contained in:
parent
f0d7392d3b
commit
39ba527ba9
|
@ -1,67 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useState } from "react"
|
||||
import { AppSidebar } from "@/components/dashboard/sidebar/app-sidebar"
|
||||
import { NavActions } from "@/components/dashboard/sidebar/nav-actions"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
} from "@/components/ui/breadcrumb"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from "@/components/ui/sidebar"
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter()
|
||||
const [currentTeamId, setCurrentTeamId] = useState<string>()
|
||||
|
||||
const handleTeamSelected = (team: any) => {
|
||||
setCurrentTeamId(team.account_id)
|
||||
// Navigate to the team dashboard if it has a slug
|
||||
if (team.slug) {
|
||||
router.push(`/dashboard/${team.slug}`)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<AppSidebar
|
||||
teamId={currentTeamId}
|
||||
onTeamSelected={handleTeamSelected}
|
||||
/>
|
||||
<SidebarInset>
|
||||
<header className="flex h-14 shrink-0 items-center gap-2">
|
||||
<div className="flex flex-1 items-center gap-2 px-3">
|
||||
<SidebarTrigger />
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="mr-2 data-[orientation=vertical]:h-4"
|
||||
/>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage className="line-clamp-1">
|
||||
Project Management & Task Tracking
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
<div className="ml-auto px-3">
|
||||
<NavActions />
|
||||
</div>
|
||||
</header>
|
||||
<div className="flex flex-1 flex-col gap-4 px-4 py-10">
|
||||
<div className="bg-muted/50 mx-auto h-24 w-full max-w-3xl rounded-xl" />
|
||||
<div className="bg-muted/50 mx-auto h-full w-full max-w-3xl rounded-xl" />
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
|
@ -222,7 +222,8 @@ function MessageContent({ content }: { content: string }) {
|
|||
}
|
||||
|
||||
export default function AgentPage({ params }: AgentPageProps) {
|
||||
const { threadId } = params;
|
||||
const resolvedParams = React.use(params as any) as { threadId: string };
|
||||
const { threadId } = resolvedParams;
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const initialMessage = searchParams.get('message');
|
||||
|
|
|
@ -1,28 +1,25 @@
|
|||
import DashboardLayout from "@/components/dashboard/DashboardLayout";
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
|
||||
interface DashboardRootLayoutProps {
|
||||
children: React.ReactNode;
|
||||
import { SidebarLeft } from "@/components/dashboard/sidebar/sidebar-left"
|
||||
import { SidebarRight } from "@/components/dashboard/sidebar/sidebar-right"
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from "@/components/ui/sidebar"
|
||||
interface DashboardLayoutProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export default async function DashboardRootLayout({
|
||||
export default async function DashboardLayout({
|
||||
children,
|
||||
}: DashboardRootLayoutProps) {
|
||||
// Get the current user via Supabase
|
||||
const supabaseClient = await createClient();
|
||||
const { data: { user } } = await supabaseClient.auth.getUser();
|
||||
|
||||
// Get the personal account details
|
||||
const { data: personalAccount } = await supabaseClient.rpc('get_personal_account');
|
||||
}: DashboardLayoutProps) {
|
||||
|
||||
return (
|
||||
<DashboardLayout
|
||||
accountId={personalAccount?.account_id || user.id}
|
||||
userName={user?.user_metadata?.name || user.email?.split('@')[0] || 'User'}
|
||||
userEmail={user.email}
|
||||
rightPanelTitle="Suna's Computer"
|
||||
>
|
||||
{children}
|
||||
</DashboardLayout>
|
||||
);
|
||||
<SidebarProvider>
|
||||
<SidebarLeft />
|
||||
<SidebarInset>
|
||||
{children}
|
||||
</SidebarInset>
|
||||
{/* <SidebarRight /> */}
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import DashboardLayout from "@/components/dashboard/DashboardLayout";
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
|
||||
interface DashboardRootLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default async function DashboardRootLayout({
|
||||
children,
|
||||
}: DashboardRootLayoutProps) {
|
||||
// Get the current user via Supabase
|
||||
const supabaseClient = await createClient();
|
||||
const { data: { user } } = await supabaseClient.auth.getUser();
|
||||
|
||||
// Get the personal account details
|
||||
const { data: personalAccount } = await supabaseClient.rpc('get_personal_account');
|
||||
|
||||
return (
|
||||
<DashboardLayout
|
||||
accountId={personalAccount?.account_id || user.id}
|
||||
userName={user?.user_metadata?.name || user.email?.split('@')[0] || 'User'}
|
||||
userEmail={user.email}
|
||||
rightPanelTitle="Suna's Computer"
|
||||
>
|
||||
{children}
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
|
@ -1,85 +1,37 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, Suspense } from 'react';
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { ChatInput } from '@/components/chat/chat-input';
|
||||
import { createProject, addUserMessage, startAgent, createThread } from "@/lib/api";
|
||||
|
||||
function DashboardContent() {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (message: string) => {
|
||||
if (!message.trim() || isSubmitting) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
// 1. Create a new project with the message as the name
|
||||
const newAgent = await createProject({
|
||||
name: message.trim().length > 50
|
||||
? message.trim().substring(0, 47) + "..."
|
||||
: message.trim(),
|
||||
description: "",
|
||||
});
|
||||
|
||||
// 2. Create a new thread for this project
|
||||
const thread = await createThread(newAgent.id);
|
||||
|
||||
// 3. Add the user message to the thread
|
||||
await addUserMessage(thread.thread_id, message.trim());
|
||||
|
||||
// 4. Start the agent with the thread ID
|
||||
const agentRun = await startAgent(thread.thread_id);
|
||||
|
||||
// 5. Navigate to the new agent's thread page
|
||||
router.push(`/dashboard/agents/${thread.thread_id}`);
|
||||
} catch (error) {
|
||||
console.error("Error creating agent:", error);
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
} from "@/components/ui/breadcrumb"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { SidebarTrigger } from "@/components/ui/sidebar"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full w-full">
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[560px] max-w-[90%]">
|
||||
<div className="text-center mb-10">
|
||||
<h1 className="text-4xl font-medium text-foreground mb-2">Hello.</h1>
|
||||
<h2 className="text-2xl text-muted-foreground">What can I help with?</h2>
|
||||
<>
|
||||
<header className="bg-background sticky top-0 flex h-14 shrink-0 items-center gap-2">
|
||||
<div className="flex flex-1 items-center gap-2 px-3">
|
||||
<SidebarTrigger />
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="mr-2 data-[orientation=vertical]:h-4"
|
||||
/>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage className="line-clamp-1">
|
||||
Project Management & Task Tracking
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
|
||||
<ChatInput
|
||||
onSubmit={handleSubmit}
|
||||
loading={isSubmitting}
|
||||
placeholder="Ask anything..."
|
||||
value={inputValue}
|
||||
onChange={setInputValue}
|
||||
/>
|
||||
</header>
|
||||
<div className="flex flex-1 flex-col gap-4 p-4">
|
||||
<div className="bg-muted/50 mx-auto h-24 w-full max-w-3xl rounded-xl" />
|
||||
<div className="bg-muted/50 mx-auto h-[100vh] w-full max-w-3xl rounded-xl" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<Suspense fallback={
|
||||
<div className="flex flex-col items-center justify-center h-full w-full">
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[560px] max-w-[90%]">
|
||||
<div className="flex flex-col items-center text-center mb-10">
|
||||
<Skeleton className="h-10 w-40 mb-2" />
|
||||
<Skeleton className="h-7 w-56" />
|
||||
</div>
|
||||
|
||||
<Skeleton className="w-full h-[100px] rounded-xl" />
|
||||
<div className="flex justify-center mt-3">
|
||||
<Skeleton className="h-5 w-16" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}>
|
||||
<DashboardContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, Suspense } from 'react';
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { ChatInput } from '@/components/chat/chat-input';
|
||||
import { createProject, addUserMessage, startAgent, createThread } from "@/lib/api";
|
||||
|
||||
function DashboardContent() {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (message: string) => {
|
||||
if (!message.trim() || isSubmitting) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
// 1. Create a new project with the message as the name
|
||||
const newAgent = await createProject({
|
||||
name: message.trim().length > 50
|
||||
? message.trim().substring(0, 47) + "..."
|
||||
: message.trim(),
|
||||
description: "",
|
||||
});
|
||||
|
||||
// 2. Create a new thread for this project
|
||||
const thread = await createThread(newAgent.id);
|
||||
|
||||
// 3. Add the user message to the thread
|
||||
await addUserMessage(thread.thread_id, message.trim());
|
||||
|
||||
// 4. Start the agent with the thread ID
|
||||
const agentRun = await startAgent(thread.thread_id);
|
||||
|
||||
// 5. Navigate to the new agent's thread page
|
||||
router.push(`/dashboard/agents/${thread.thread_id}`);
|
||||
} catch (error) {
|
||||
console.error("Error creating agent:", error);
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full w-full">
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[560px] max-w-[90%]">
|
||||
<div className="text-center mb-10">
|
||||
<h1 className="text-4xl font-medium text-foreground mb-2">Hello.</h1>
|
||||
<h2 className="text-2xl text-muted-foreground">What can I help with?</h2>
|
||||
</div>
|
||||
|
||||
<ChatInput
|
||||
onSubmit={handleSubmit}
|
||||
loading={isSubmitting}
|
||||
placeholder="Ask anything..."
|
||||
value={inputValue}
|
||||
onChange={setInputValue}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<Suspense fallback={
|
||||
<div className="flex flex-col items-center justify-center h-full w-full">
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[560px] max-w-[90%]">
|
||||
<div className="flex flex-col items-center text-center mb-10">
|
||||
<Skeleton className="h-10 w-40 mb-2" />
|
||||
<Skeleton className="h-7 w-56" />
|
||||
</div>
|
||||
|
||||
<Skeleton className="w-full h-[100px] rounded-xl" />
|
||||
<div className="flex justify-center mt-3">
|
||||
<Skeleton className="h-5 w-16" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}>
|
||||
<DashboardContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
|
@ -1,276 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
AudioWaveform,
|
||||
Blocks,
|
||||
Calendar,
|
||||
Command,
|
||||
Home,
|
||||
Inbox,
|
||||
MessageCircleQuestion,
|
||||
Search,
|
||||
Settings2,
|
||||
Sparkles,
|
||||
Trash2,
|
||||
} from "lucide-react"
|
||||
|
||||
import { NavFavorites } from "@/components/nav-favorites"
|
||||
import { NavMain } from "@/components/nav-main"
|
||||
import { NavSecondary } from "@/components/nav-secondary"
|
||||
import { NavWorkspaces } from "@/components/nav-workspaces"
|
||||
import { TeamSwitcher } from "@/components/dashboard/sidebar/team-switcher"
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarHeader,
|
||||
SidebarRail,
|
||||
} from "@/components/ui/sidebar"
|
||||
|
||||
// This is sample data.
|
||||
const data = {
|
||||
teams: [
|
||||
{
|
||||
name: "Acme Inc",
|
||||
logo: Command,
|
||||
plan: "Enterprise",
|
||||
},
|
||||
{
|
||||
name: "Acme Corp.",
|
||||
logo: AudioWaveform,
|
||||
plan: "Startup",
|
||||
},
|
||||
{
|
||||
name: "Evil Corp.",
|
||||
logo: Command,
|
||||
plan: "Free",
|
||||
},
|
||||
],
|
||||
navMain: [
|
||||
{
|
||||
title: "Search",
|
||||
url: "#",
|
||||
icon: Search,
|
||||
},
|
||||
{
|
||||
title: "Ask AI",
|
||||
url: "#",
|
||||
icon: Sparkles,
|
||||
},
|
||||
{
|
||||
title: "Home",
|
||||
url: "#",
|
||||
icon: Home,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
title: "Inbox",
|
||||
url: "#",
|
||||
icon: Inbox,
|
||||
badge: "10",
|
||||
},
|
||||
],
|
||||
navSecondary: [
|
||||
{
|
||||
title: "Calendar",
|
||||
url: "#",
|
||||
icon: Calendar,
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
url: "#",
|
||||
icon: Settings2,
|
||||
},
|
||||
{
|
||||
title: "Templates",
|
||||
url: "#",
|
||||
icon: Blocks,
|
||||
},
|
||||
{
|
||||
title: "Trash",
|
||||
url: "#",
|
||||
icon: Trash2,
|
||||
},
|
||||
{
|
||||
title: "Help",
|
||||
url: "#",
|
||||
icon: MessageCircleQuestion,
|
||||
},
|
||||
],
|
||||
favorites: [
|
||||
{
|
||||
name: "Project Management & Task Tracking",
|
||||
url: "#",
|
||||
emoji: "📊",
|
||||
},
|
||||
{
|
||||
name: "Family Recipe Collection & Meal Planning",
|
||||
url: "#",
|
||||
emoji: "🍳",
|
||||
},
|
||||
{
|
||||
name: "Fitness Tracker & Workout Routines",
|
||||
url: "#",
|
||||
emoji: "💪",
|
||||
},
|
||||
{
|
||||
name: "Book Notes & Reading List",
|
||||
url: "#",
|
||||
emoji: "📚",
|
||||
},
|
||||
{
|
||||
name: "Sustainable Gardening Tips & Plant Care",
|
||||
url: "#",
|
||||
emoji: "🌱",
|
||||
},
|
||||
{
|
||||
name: "Language Learning Progress & Resources",
|
||||
url: "#",
|
||||
emoji: "🗣️",
|
||||
},
|
||||
{
|
||||
name: "Home Renovation Ideas & Budget Tracker",
|
||||
url: "#",
|
||||
emoji: "🏠",
|
||||
},
|
||||
{
|
||||
name: "Personal Finance & Investment Portfolio",
|
||||
url: "#",
|
||||
emoji: "💰",
|
||||
},
|
||||
{
|
||||
name: "Movie & TV Show Watchlist with Reviews",
|
||||
url: "#",
|
||||
emoji: "🎬",
|
||||
},
|
||||
{
|
||||
name: "Daily Habit Tracker & Goal Setting",
|
||||
url: "#",
|
||||
emoji: "✅",
|
||||
},
|
||||
],
|
||||
workspaces: [
|
||||
{
|
||||
name: "Personal Life Management",
|
||||
emoji: "🏠",
|
||||
pages: [
|
||||
{
|
||||
name: "Daily Journal & Reflection",
|
||||
url: "#",
|
||||
emoji: "📔",
|
||||
},
|
||||
{
|
||||
name: "Health & Wellness Tracker",
|
||||
url: "#",
|
||||
emoji: "🍏",
|
||||
},
|
||||
{
|
||||
name: "Personal Growth & Learning Goals",
|
||||
url: "#",
|
||||
emoji: "🌟",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Professional Development",
|
||||
emoji: "💼",
|
||||
pages: [
|
||||
{
|
||||
name: "Career Objectives & Milestones",
|
||||
url: "#",
|
||||
emoji: "🎯",
|
||||
},
|
||||
{
|
||||
name: "Skill Acquisition & Training Log",
|
||||
url: "#",
|
||||
emoji: "🧠",
|
||||
},
|
||||
{
|
||||
name: "Networking Contacts & Events",
|
||||
url: "#",
|
||||
emoji: "🤝",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Creative Projects",
|
||||
emoji: "🎨",
|
||||
pages: [
|
||||
{
|
||||
name: "Writing Ideas & Story Outlines",
|
||||
url: "#",
|
||||
emoji: "✍️",
|
||||
},
|
||||
{
|
||||
name: "Art & Design Portfolio",
|
||||
url: "#",
|
||||
emoji: "🖼️",
|
||||
},
|
||||
{
|
||||
name: "Music Composition & Practice Log",
|
||||
url: "#",
|
||||
emoji: "🎵",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Home Management",
|
||||
emoji: "🏡",
|
||||
pages: [
|
||||
{
|
||||
name: "Household Budget & Expense Tracking",
|
||||
url: "#",
|
||||
emoji: "💰",
|
||||
},
|
||||
{
|
||||
name: "Home Maintenance Schedule & Tasks",
|
||||
url: "#",
|
||||
emoji: "🔧",
|
||||
},
|
||||
{
|
||||
name: "Family Calendar & Event Planning",
|
||||
url: "#",
|
||||
emoji: "📅",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Travel & Adventure",
|
||||
emoji: "🧳",
|
||||
pages: [
|
||||
{
|
||||
name: "Trip Planning & Itineraries",
|
||||
url: "#",
|
||||
emoji: "🗺️",
|
||||
},
|
||||
{
|
||||
name: "Travel Bucket List & Inspiration",
|
||||
url: "#",
|
||||
emoji: "🌎",
|
||||
},
|
||||
{
|
||||
name: "Travel Journal & Photo Gallery",
|
||||
url: "#",
|
||||
emoji: "📸",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
<Sidebar className="border-r-0" {...props}>
|
||||
<SidebarHeader>
|
||||
<TeamSwitcher teams={data.teams} />
|
||||
<NavMain items={data.navMain} />
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<NavFavorites favorites={data.favorites} />
|
||||
<NavWorkspaces workspaces={data.workspaces} />
|
||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||
</SidebarContent>
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import {
|
||||
ArrowUpRight,
|
||||
Link as LinkIcon,
|
||||
MoreHorizontal,
|
||||
Trash2,
|
||||
StarOff,
|
||||
} from "lucide-react"
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar"
|
||||
import { getProjects, getThreads } from "@/lib/api"
|
||||
import Link from "next/link"
|
||||
|
||||
export function NavAgents() {
|
||||
const { isMobile } = useSidebar()
|
||||
const [agents, setAgents] = useState<{name: string, url: string, emoji: string}[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
// Load agents dynamically from the API
|
||||
useEffect(() => {
|
||||
async function loadAgents() {
|
||||
try {
|
||||
const projectsData = await getProjects()
|
||||
const agentsList = []
|
||||
|
||||
for (const project of projectsData) {
|
||||
const threads = await getThreads(project.id)
|
||||
if (threads && threads.length > 0) {
|
||||
// For each thread in the project, create an agent entry
|
||||
for (const thread of threads) {
|
||||
// Generate a simple emoji based on the project name hash
|
||||
const emoji = getEmojiFromName(project.name)
|
||||
|
||||
agentsList.push({
|
||||
name: project.name,
|
||||
url: `/dashboard/agents/${thread.thread_id}`,
|
||||
emoji: emoji
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setAgents(agentsList)
|
||||
} catch (err) {
|
||||
console.error("Error loading agents for sidebar:", err)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
loadAgents()
|
||||
}, [])
|
||||
|
||||
// Function to generate emoji from name
|
||||
const getEmojiFromName = (name: string) => {
|
||||
const emojis = ["📊", "📝", "💼", "🔍", "✅", "📈", "💡", "🎯", "🗂️", "🤖", "💬", "📚"]
|
||||
// Simple hash function to pick a consistent emoji for the same name
|
||||
let hash = 0
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
hash = name.charCodeAt(i) + ((hash << 5) - hash)
|
||||
}
|
||||
return emojis[Math.abs(hash) % emojis.length]
|
||||
}
|
||||
|
||||
// Get only the latest 20 agents for the sidebar
|
||||
const recentAgents = agents.slice(0, 20)
|
||||
|
||||
return (
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||
<div className="flex justify-between items-center">
|
||||
<SidebarGroupLabel>Agents</SidebarGroupLabel>
|
||||
</div>
|
||||
|
||||
<SidebarMenu>
|
||||
{isLoading ? (
|
||||
// Show skeleton loaders while loading
|
||||
Array.from({length: 3}).map((_, index) => (
|
||||
<SidebarMenuItem key={`skeleton-${index}`}>
|
||||
<SidebarMenuButton>
|
||||
<div className="h-4 w-4 bg-sidebar-foreground/10 rounded-md animate-pulse"></div>
|
||||
<div className="h-3 bg-sidebar-foreground/10 rounded w-3/4 animate-pulse"></div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))
|
||||
) : recentAgents.length > 0 ? (
|
||||
// Show agents
|
||||
<>
|
||||
{recentAgents.map((item, index) => (
|
||||
<SidebarMenuItem key={`agent-${index}`}>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href={item.url} title={item.name}>
|
||||
<span>{item.emoji}</span>
|
||||
<span>{item.name}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuAction showOnHover>
|
||||
<MoreHorizontal />
|
||||
<span className="sr-only">More</span>
|
||||
</SidebarMenuAction>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-56 rounded-lg"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align={isMobile ? "end" : "start"}
|
||||
>
|
||||
<DropdownMenuItem>
|
||||
<StarOff className="text-muted-foreground" />
|
||||
<span>Remove from agents</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<LinkIcon className="text-muted-foreground" />
|
||||
<span>Copy Link</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild>
|
||||
<a href={item.url} target="_blank" rel="noopener noreferrer">
|
||||
<ArrowUpRight className="text-muted-foreground" />
|
||||
<span>Open in New Tab</span>
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<Trash2 className="text-muted-foreground" />
|
||||
<span>Delete</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
|
||||
{agents.length > 20 && (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild className="text-sidebar-foreground/70">
|
||||
<Link href="/dashboard/agents">
|
||||
<MoreHorizontal />
|
||||
<span>See all agents</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
// Empty state
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="text-sidebar-foreground/70">
|
||||
<span>No agents yet</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
"use client"
|
||||
|
||||
import { type LucideIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar"
|
||||
|
||||
export function NavMain({
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
isActive?: boolean
|
||||
}[]
|
||||
}) {
|
||||
return (
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild isActive={item.isActive}>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import React from "react"
|
||||
import { type LucideIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuBadge,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar"
|
||||
|
||||
export function NavSecondary({
|
||||
items,
|
||||
...props
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
badge?: React.ReactNode
|
||||
}[]
|
||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
{item.badge && <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>}
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
"use client"
|
||||
|
||||
import {
|
||||
BadgeCheck,
|
||||
Bell,
|
||||
ChevronsUpDown,
|
||||
CreditCard,
|
||||
LogOut,
|
||||
Settings,
|
||||
User,
|
||||
} from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/components/ui/avatar"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar"
|
||||
import Link from "next/link"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { createClient } from "@/lib/supabase/client"
|
||||
|
||||
export function NavUser({
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
name: string
|
||||
email: string
|
||||
avatar: string
|
||||
}
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
const router = useRouter()
|
||||
|
||||
const handleLogout = async () => {
|
||||
const supabase = createClient()
|
||||
await supabase.auth.signOut()
|
||||
router.push("/auth")
|
||||
}
|
||||
|
||||
const getInitials = (name: string) => {
|
||||
return name.split(' ')
|
||||
.map(part => part.charAt(0))
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
.substring(0, 2)
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">{getInitials(user.name)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="truncate text-xs">{user.email}</span>
|
||||
</div>
|
||||
<ChevronsUpDown className="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align="start"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">{getInitials(user.name)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="truncate text-xs">{user.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/dashboard/settings/billing">
|
||||
<CreditCard className="mr-2 h-4 w-4" />
|
||||
Billing
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/dashboard/settings">
|
||||
<Settings className="mr-2 h-4 w-4" />
|
||||
Settings
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={handleLogout}>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
BookOpen,
|
||||
CalendarClock,
|
||||
HelpCircle,
|
||||
MessageCircleQuestion,
|
||||
} from "lucide-react"
|
||||
|
||||
import { NavAgents } from "@/components/dashboard/sidebar/nav-agents"
|
||||
import { NavUser } from "@/components/dashboard/sidebar/nav-user"
|
||||
import { NavSecondary } from "@/components/dashboard/sidebar/nav-secondary"
|
||||
import { TeamSwitcher } from "@/components/dashboard/sidebar/team-switcher"
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarRail,
|
||||
} from "@/components/ui/sidebar"
|
||||
import { useEffect, useState } from "react"
|
||||
import { createClient } from "@/lib/supabase/client"
|
||||
|
||||
// Only keep necessary data
|
||||
const navSecondaryItems = [
|
||||
{
|
||||
title: "Help",
|
||||
url: "#",
|
||||
icon: HelpCircle,
|
||||
},
|
||||
{
|
||||
title: "Careers",
|
||||
url: "#",
|
||||
icon: BookOpen,
|
||||
},
|
||||
{
|
||||
title: "Book Demo",
|
||||
url: "#",
|
||||
icon: CalendarClock,
|
||||
},
|
||||
]
|
||||
|
||||
export function SidebarLeft({
|
||||
...props
|
||||
}: React.ComponentProps<typeof Sidebar>) {
|
||||
const [user, setUser] = useState<{
|
||||
name: string;
|
||||
email: string;
|
||||
avatar: string;
|
||||
}>({
|
||||
name: "Loading...",
|
||||
email: "loading@example.com",
|
||||
avatar: ""
|
||||
})
|
||||
|
||||
// Fetch user data
|
||||
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()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Sidebar className="border-r-0" {...props}>
|
||||
<SidebarHeader>
|
||||
<TeamSwitcher />
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<NavAgents />
|
||||
<NavSecondary items={navSecondaryItems} className="mt-auto" />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<NavUser user={user} />
|
||||
</SidebarFooter>
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
import * as React from "react"
|
||||
import { Plus } from "lucide-react"
|
||||
|
||||
import { Calendars } from "@/components/dashboard/sidebar/calendars"
|
||||
import { DatePicker } from "@/components/dashboard/sidebar/date-picker"
|
||||
import { NavUser } from "@/components/dashboard/sidebar/nav-user"
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarRail,
|
||||
SidebarSeparator,
|
||||
} from "@/components/ui/sidebar"
|
||||
|
||||
// This is sample data.
|
||||
const data = {
|
||||
user: {
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
avatar: "/avatars/shadcn.jpg",
|
||||
},
|
||||
calendars: [
|
||||
{
|
||||
name: "My Calendars",
|
||||
items: ["Personal", "Work", "Family"],
|
||||
},
|
||||
{
|
||||
name: "Favorites",
|
||||
items: ["Holidays", "Birthdays"],
|
||||
},
|
||||
{
|
||||
name: "Other",
|
||||
items: ["Travel", "Reminders", "Deadlines"],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export function SidebarRight({
|
||||
...props
|
||||
}: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
<Sidebar
|
||||
collapsible="none"
|
||||
className="sticky top-0 hidden h-svh border-l lg:flex"
|
||||
{...props}
|
||||
>
|
||||
<SidebarHeader className="border-sidebar-border h-16 border-b">
|
||||
<NavUser user={data.user} />
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<DatePicker />
|
||||
<SidebarSeparator className="mx-0" />
|
||||
<Calendars calendars={data.calendars} />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton>
|
||||
<Plus />
|
||||
<span>New Calendar</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { ChevronDown, Plus, Command, AudioWaveform } from "lucide-react"
|
||||
import { useAccounts } from "@/hooks/use-accounts"
|
||||
import NewTeamForm from "@/components/basejump/new-team-form"
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
|
||||
export function TeamSwitcher() {
|
||||
const router = useRouter()
|
||||
const { data: accounts } = useAccounts()
|
||||
const [showNewTeamDialog, setShowNewTeamDialog] = React.useState(false)
|
||||
|
||||
// Prepare personal account and team accounts
|
||||
const personalAccount = React.useMemo(() => accounts?.find(account => account.personal_account), [accounts])
|
||||
const teamAccounts = React.useMemo(() => accounts?.filter(account => !account.personal_account), [accounts])
|
||||
|
||||
// Create a default list of teams with logos for the UI (will show until real data loads)
|
||||
const defaultTeams = [
|
||||
{
|
||||
name: personalAccount?.name || "Personal Account",
|
||||
logo: Command,
|
||||
plan: "Personal",
|
||||
account_id: personalAccount?.account_id,
|
||||
slug: personalAccount?.slug,
|
||||
personal_account: true
|
||||
},
|
||||
...(teamAccounts?.map(team => ({
|
||||
name: team.name,
|
||||
logo: AudioWaveform,
|
||||
plan: "Team",
|
||||
account_id: team.account_id,
|
||||
slug: team.slug,
|
||||
personal_account: false
|
||||
})) || [])
|
||||
]
|
||||
|
||||
// Use the first team or first entry in defaultTeams as activeTeam
|
||||
const [activeTeam, setActiveTeam] = React.useState(defaultTeams[0])
|
||||
|
||||
// Update active team when accounts load
|
||||
React.useEffect(() => {
|
||||
if (accounts?.length) {
|
||||
const currentTeam = accounts.find(account => account.account_id === activeTeam.account_id)
|
||||
if (currentTeam) {
|
||||
setActiveTeam({
|
||||
name: currentTeam.name,
|
||||
logo: currentTeam.personal_account ? Command : AudioWaveform,
|
||||
plan: currentTeam.personal_account ? "Personal" : "Team",
|
||||
account_id: currentTeam.account_id,
|
||||
slug: currentTeam.slug,
|
||||
personal_account: currentTeam.personal_account
|
||||
})
|
||||
} else {
|
||||
// If current team not found, set first available account as active
|
||||
const firstAccount = accounts[0]
|
||||
setActiveTeam({
|
||||
name: firstAccount.name,
|
||||
logo: firstAccount.personal_account ? Command : AudioWaveform,
|
||||
plan: firstAccount.personal_account ? "Personal" : "Team",
|
||||
account_id: firstAccount.account_id,
|
||||
slug: firstAccount.slug,
|
||||
personal_account: firstAccount.personal_account
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [accounts, activeTeam.account_id])
|
||||
|
||||
// Handle team selection
|
||||
const handleTeamSelect = (team) => {
|
||||
setActiveTeam(team)
|
||||
|
||||
// Navigate to the appropriate dashboard
|
||||
if (team.personal_account) {
|
||||
router.push('/dashboard')
|
||||
} else {
|
||||
router.push(`/dashboard/${team.slug}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!activeTeam) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={showNewTeamDialog} onOpenChange={setShowNewTeamDialog}>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton className="w-fit px-1.5">
|
||||
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-5 items-center justify-center rounded-md">
|
||||
<activeTeam.logo className="size-3" />
|
||||
</div>
|
||||
<span className="truncate font-medium">{activeTeam.name}</span>
|
||||
<ChevronDown className="opacity-50" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-64 rounded-lg"
|
||||
align="start"
|
||||
side="bottom"
|
||||
sideOffset={4}
|
||||
>
|
||||
{personalAccount && (
|
||||
<>
|
||||
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
||||
Personal Account
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
key={personalAccount.account_id}
|
||||
onClick={() => handleTeamSelect({
|
||||
name: personalAccount.name,
|
||||
logo: Command,
|
||||
plan: "Personal",
|
||||
account_id: personalAccount.account_id,
|
||||
slug: personalAccount.slug,
|
||||
personal_account: true
|
||||
})}
|
||||
className="gap-2 p-2"
|
||||
>
|
||||
<div className="flex size-6 items-center justify-center rounded-xs border">
|
||||
<Command className="size-4 shrink-0" />
|
||||
</div>
|
||||
{personalAccount.name}
|
||||
<DropdownMenuShortcut>⌘1</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
{teamAccounts?.length > 0 && (
|
||||
<>
|
||||
<DropdownMenuLabel className="text-muted-foreground text-xs mt-2">
|
||||
Teams
|
||||
</DropdownMenuLabel>
|
||||
{teamAccounts.map((team, index) => (
|
||||
<DropdownMenuItem
|
||||
key={team.account_id}
|
||||
onClick={() => handleTeamSelect({
|
||||
name: team.name,
|
||||
logo: AudioWaveform,
|
||||
plan: "Team",
|
||||
account_id: team.account_id,
|
||||
slug: team.slug,
|
||||
personal_account: false
|
||||
})}
|
||||
className="gap-2 p-2"
|
||||
>
|
||||
<div className="flex size-6 items-center justify-center rounded-xs border">
|
||||
<AudioWaveform className="size-4 shrink-0" />
|
||||
</div>
|
||||
{team.name}
|
||||
<DropdownMenuShortcut>⌘{index + 2}</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
className="gap-2 p-2"
|
||||
onClick={() => {
|
||||
setShowNewTeamDialog(true)
|
||||
}}
|
||||
>
|
||||
<div className="bg-background flex size-6 items-center justify-center rounded-md border">
|
||||
<Plus className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground font-medium">Add team</div>
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
|
||||
<DialogContent className="sm:max-w-[425px] border-subtle dark:border-white/10 bg-card-bg dark:bg-background-secondary rounded-2xl shadow-custom">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-foreground">Create a new team</DialogTitle>
|
||||
<DialogDescription className="text-foreground/70">
|
||||
Create a team to collaborate with others.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<NewTeamForm />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue