Ctrl K
ComunidadGuías…

components/user-menu.tsx

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
'use client'

import { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'

import {
  IconChartBar as ChartBar,
  IconLink as Link2,
  IconLogout as LogOut,
  IconShield,
  IconUserCircle as UserRound
} from '@tabler/icons-react'

import { signOut } from '@/lib/actions/auth'
import type { AppUser } from '@/lib/auth/types'
import { chatStore } from '@/lib/chat-store'

import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'

import { AccountSettingsDialog } from '@/components/account-settings-dialog'
import { useUsageBudget } from '@/components/usage-budget-provider'
import { UsageDialog } from '@/components/usage-dialog'

import { Button } from './ui/button'
import { ExternalLinkItems } from './external-link-items'

interface UserMenuProps {
  user: AppUser
}

export default function UserMenu({ user }: UserMenuProps) {
  const router = useRouter()
  const [menuOpen, setMenuOpen] = useState(false)
  const [accountOpen, setAccountOpen] = useState(false)
  const [usageOpen, setUsageOpen] = useState(false)
  const { usage, isLow, isExhausted, refreshUsage } = useUsageBudget()
  const userName = user.name || 'User'
  const avatarUrl = user.image ?? undefined

  const getInitials = (name: string, email: string | null | undefined) => {
    if (name && name !== 'User') {
      const names = name.split(' ')
      if (names.length > 1) {
        return `${names[0][0]}${names[names.length - 1][0]}`.toUpperCase()
      }
      return name.substring(0, 2).toUpperCase()
    }
    if (email) {
      return email.split('@')[0].substring(0, 2).toUpperCase()
    }
    return 'U'
  }

  const handleLogout = async () => {
    await signOut()
    chatStore.setUser(null)
    router.push('/')
    router.refresh()
  }

  const handleOpenAccount = () => {
    setMenuOpen(false)
    window.setTimeout(() => setAccountOpen(true), 0)
  }

  const handleOpenUsage = () => {
    setMenuOpen(false)
    window.setTimeout(() => {
      setUsageOpen(true)
      void refreshUsage()
    }, 0)
  }

  const accountMenuLabel = isExhausted
    ? 'Open account menu. Monthly usage limit reached.'
    : isLow
      ? 'Open account menu. Monthly usage is running low.'
      : 'Open account menu'

  return (
    <>
      <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen} modal={false}>
        <DropdownMenuTrigger asChild>
          <Button
            variant="ghost"
            className="relative size-6 rounded-full"
            aria-label={accountMenuLabel}
          >
            <Avatar className="size-6">
              <AvatarImage src={avatarUrl} alt={userName} />
              <AvatarFallback>
                {getInitials(userName, user.email)}
              </AvatarFallback>
            </Avatar>
            {isLow && (
              <span
                className="absolute -right-0.5 -top-0.5 size-2 rounded-full bg-destructive ring-2 ring-background"
                aria-hidden="true"
                data-testid="usage-status-dot"
              />
            )}
          </Button>
        </DropdownMenuTrigger>
        <DropdownMenuContent className="w-60" align="end" forceMount>
          <DropdownMenuLabel className="font-normal">
            <div className="flex flex-col space-y-1">
              <p className="text-sm font-medium leading-none truncate">
                {userName}
              </p>
              <p className="text-xs leading-none text-muted-foreground truncate">
                {user.email}
              </p>
            </div>
          </DropdownMenuLabel>
          <DropdownMenuSeparator />
          <DropdownMenuItem
            onSelect={event => {
              event.preventDefault()
              handleOpenAccount()
            }}
          >
            <UserRound className="size-4" />
            <span>Account</span>
          </DropdownMenuItem>
          {user.role === 'admin' && (
            <DropdownMenuItem asChild>
              <Link href="/auth/admin">
                <IconShield className="size-4" />
                <span>Admin</span>
              </Link>
            </DropdownMenuItem>
          )}
          {usage && (
            <DropdownMenuItem
              onSelect={event => {
                event.preventDefault()
                handleOpenUsage()
              }}
            >
              <ChartBar className="size-4" />
              <span>Usage</span>
              <span className="ml-auto text-xs text-muted-foreground tabular-nums">
                {usage.remaining} / {usage.limit}
              </span>
            </DropdownMenuItem>
          )}
          <DropdownMenuSub>
            <DropdownMenuSubTrigger>
              <Link2 className="size-4" />
              <span>Links</span>
            </DropdownMenuSubTrigger>
            <DropdownMenuSubContent>
              <ExternalLinkItems />
            </DropdownMenuSubContent>
          </DropdownMenuSub>
          <DropdownMenuSeparator />
          <DropdownMenuItem onClick={handleLogout}>
            <LogOut className="size-4" />
            <span>Logout</span>
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      <AccountSettingsDialog
        open={accountOpen}
        onOpenChange={setAccountOpen}
        user={user}
      />
      {usage && (
        <UsageDialog
          open={usageOpen}
          onOpenChange={setUsageOpen}
          usage={usage}
        />
      )}
    </>
  )
}