suna/frontend/src/components/dashboard/sidebar/nav-user-with-teams.tsx

300 lines
10 KiB
TypeScript
Raw Normal View History

2025-04-16 02:14:58 +08:00
"use client"
import * as React from "react"
import { useRouter } from "next/navigation"
2025-04-16 04:45:46 +08:00
import Link from "next/link"
import {
BadgeCheck,
Bell,
ChevronDown,
ChevronsUpDown,
Command,
CreditCard,
LogOut,
Plus,
Settings,
User,
AudioWaveform,
} from "lucide-react"
2025-04-16 02:14:58 +08:00
import { useAccounts } from "@/hooks/use-accounts"
import NewTeamForm from "@/components/basejump/new-team-form"
2025-04-16 04:45:46 +08:00
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar"
2025-04-16 02:14:58 +08:00
import {
DropdownMenu,
DropdownMenuContent,
2025-04-16 04:45:46 +08:00
DropdownMenuGroup,
2025-04-16 02:14:58 +08:00
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
2025-04-16 04:45:46 +08:00
useSidebar,
2025-04-16 02:14:58 +08:00
} from "@/components/ui/sidebar"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
2025-04-16 04:45:46 +08:00
import { createClient } from "@/lib/supabase/client"
2025-04-16 02:14:58 +08:00
2025-04-16 04:45:46 +08:00
export function NavUserWithTeams({
user,
}: {
user: {
name: string
email: string
avatar: string
}
}) {
2025-04-16 02:14:58 +08:00
const router = useRouter()
2025-04-16 04:45:46 +08:00
const { isMobile } = useSidebar()
2025-04-16 02:14:58 +08:00
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}`)
}
}
2025-04-16 04:45:46 +08:00
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)
}
2025-04-16 02:14:58 +08:00
if (!activeTeam) {
return null
}
return (
<Dialog open={showNewTeamDialog} onOpenChange={setShowNewTeamDialog}>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
2025-04-16 04:45:46 +08:00
<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>
2025-04-16 02:14:58 +08:00
</div>
2025-04-16 04:45:46 +08:00
<ChevronsUpDown className="ml-auto size-4" />
2025-04-16 02:14:58 +08:00
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
2025-04-16 04:45:46 +08:00
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
side={isMobile ? "bottom" : "right"}
2025-04-16 02:14:58 +08:00
align="start"
sideOffset={4}
>
2025-04-16 04:45:46 +08:00
<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 />
{/* Teams Section */}
2025-04-16 02:14:58 +08:00
{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>
2025-04-16 04:45:46 +08:00
<DropdownMenuSeparator />
{/* User Settings Section */}
<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>
2025-04-16 02:14:58 +08:00
</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>
)
2025-04-16 04:45:46 +08:00
}