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,
|
2025-04-16 08:04:04 +08:00
|
|
|
Sun,
|
|
|
|
Moon,
|
2025-04-16 04:45:46 +08:00
|
|
|
} 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 08:04:04 +08:00
|
|
|
import { useTheme } from "next-themes"
|
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)
|
2025-04-16 08:04:04 +08:00
|
|
|
const { theme, setTheme } = useTheme()
|
2025-04-16 02:14:58 +08:00
|
|
|
|
|
|
|
// 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"
|
2025-04-16 15:16:38 +08:00
|
|
|
side={isMobile ? "bottom" : "top"}
|
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>
|
|
|
|
))}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
|
2025-04-16 09:34:56 +08:00
|
|
|
{/* <DropdownMenuSeparator />
|
2025-04-16 02:14:58 +08:00
|
|
|
<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>
|
2025-04-16 09:34:56 +08:00
|
|
|
</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>
|
2025-04-16 08:04:04 +08:00
|
|
|
<DropdownMenuItem onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
<Sun className="mr-2 h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
|
|
|
<Moon className="absolute mr-2 h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
|
|
|
<span>Theme</span>
|
|
|
|
</div>
|
|
|
|
</DropdownMenuItem>
|
2025-04-16 04:45:46 +08:00
|
|
|
</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
|
|
|
}
|