import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { BookOpen, LogOut, type LucideIcon, Moon, Settings, UserRound, Sun, UserRoundPen, } from "lucide-react"; import type { DeploymentMode } from "@paperclipai/shared"; import { Link } from "@/lib/router"; import { authApi } from "@/api/auth"; import { queryKeys } from "@/lib/queryKeys"; import { useSidebar } from "../context/SidebarContext"; import { useTheme } from "../context/ThemeContext"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { cn } from "../lib/utils"; const PROFILE_SETTINGS_PATH = "/instance/settings/profile"; const DOCS_URL = "https://docs.paperclip.ing/"; interface SidebarAccountMenuProps { deploymentMode?: DeploymentMode; instanceSettingsTarget: string; open?: boolean; onOpenChange?: (open: boolean) => void; version?: string | null; } interface MenuActionProps { label: string; description: string; icon: LucideIcon; onClick?: () => void; href?: string; external?: boolean; } function deriveInitials(name: string) { const parts = name.trim().split(/\s+/).filter(Boolean); if (parts.length >= 2) { return `${parts[0]?.[0] ?? ""}${parts[parts.length - 1]?.[0] ?? ""}`.toUpperCase(); } return name.slice(0, 2).toUpperCase(); } function deriveUserSlug(name: string | null | undefined, email: string | null | undefined, id: string | null | undefined) { const candidates = [name, email?.split("@")[0], email, id]; for (const candidate of candidates) { const slug = candidate ?.trim() .toLowerCase() .replace(/['"]/g, "") .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, ""); if (slug) return slug; } return "me"; } function MenuAction({ label, description, icon: Icon, onClick, href, external = false }: MenuActionProps) { const className = "flex w-full items-start gap-3 rounded-xl px-3 py-3 text-left transition-colors hover:bg-accent/60"; const content = ( <> {label} {description} ); if (href) { if (external) { return ( {content} ); } return ( {content} ); } return ( ); } export function SidebarAccountMenu({ deploymentMode, instanceSettingsTarget, open: controlledOpen, onOpenChange, version, }: SidebarAccountMenuProps) { const [internalOpen, setInternalOpen] = useState(false); const queryClient = useQueryClient(); const { isMobile, setSidebarOpen } = useSidebar(); const { theme, toggleTheme } = useTheme(); const open = controlledOpen ?? internalOpen; const setOpen = onOpenChange ?? setInternalOpen; const { data: session } = useQuery({ queryKey: queryKeys.auth.session, queryFn: () => authApi.getSession(), retry: false, }); const signOutMutation = useMutation({ mutationFn: () => authApi.signOut(), onSuccess: async () => { setOpen(false); await queryClient.invalidateQueries({ queryKey: queryKeys.auth.session }); }, }); const displayName = session?.user.name?.trim() || "Board"; const secondaryLabel = session?.user.email?.trim() || (deploymentMode === "authenticated" ? "Signed in" : "Local workspace board"); const accountBadge = deploymentMode === "authenticated" ? "Account" : "Local"; const initials = deriveInitials(displayName); const profileHref = `/u/${deriveUserSlug(session?.user.name, session?.user.email, session?.user.id)}`; function closeNavigationChrome() { setOpen(false); if (isMobile) setSidebarOpen(false); } return (
{session?.user.image ? : null} {initials}

{displayName}

{accountBadge}

{secondaryLabel}

{version ? (

Paperclip v{version}

) : null}
setOpen(false)} /> { toggleTheme(); setOpen(false); }} /> {deploymentMode === "authenticated" ? ( ) : null}
); }