import { useCallback, useMemo, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Check, ChevronsUpDown, GripVertical, LogOut, Plus, Settings, UserPlus, } from "lucide-react"; import { DndContext, MouseSensor, TouchSensor, closestCenter, type DragEndEvent, useSensor, useSensors, } from "@dnd-kit/core"; import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import type { Company } from "@paperclipai/shared"; import { Link, useLocation, useNavigate } from "@/lib/router"; import { authApi } from "@/api/auth"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { useCompany } from "@/context/CompanyContext"; import { useDialogActions } from "@/context/DialogContext"; import { useCompanyOrder } from "@/hooks/useCompanyOrder"; import { queryKeys } from "@/lib/queryKeys"; import { cn } from "@/lib/utils"; import { useSidebar } from "../context/SidebarContext"; import { CompanyPatternIcon } from "./CompanyPatternIcon"; interface SidebarCompanyMenuProps { open?: boolean; onOpenChange?: (open: boolean) => void; } function WorkspaceIcon({ company }: { company: Company }) { return ( ); } function SortableCompanyItem({ company, isEditing, isSelected, onSelect, }: { company: Company; isEditing: boolean; isSelected: boolean; onSelect: (company: Company) => void; }) { const { attributes, listeners, setActivatorNodeRef, setNodeRef, transform, transition, isDragging, } = useSortable({ id: company.id, disabled: !isEditing }); return ( { if (isEditing) { event.preventDefault(); return; } onSelect(company); }} className={cn( "min-w-0 gap-2 py-2", isEditing && "cursor-grab", isDragging && "opacity-80", isSelected && "bg-accent text-accent-foreground", )} > {company.name} {isEditing ? ( ) : ( <> {company.issuePrefix} {isSelected ? : null} )} ); } export function SidebarCompanyMenu({ open: controlledOpen, onOpenChange }: SidebarCompanyMenuProps = {}) { const [internalOpen, setInternalOpen] = useState(false); const [isEditingOrder, setIsEditingOrder] = useState(false); const queryClient = useQueryClient(); const { companies, selectedCompany, setSelectedCompanyId } = useCompany(); const { openOnboarding } = useDialogActions(); const { isMobile, setSidebarOpen } = useSidebar(); const location = useLocation(); const navigate = useNavigate(); const open = controlledOpen ?? internalOpen; const setOpen = onOpenChange ?? setInternalOpen; const sensors = useSensors( useSensor(MouseSensor, { activationConstraint: { distance: 8 }, }), useSensor(TouchSensor, { activationConstraint: { delay: 180, tolerance: 6 }, }), ); const sidebarCompanies = useMemo( () => companies.filter((company) => company.status !== "archived"), [companies], ); const { data: session } = useQuery({ queryKey: queryKeys.auth.session, queryFn: () => authApi.getSession(), retry: false, }); const currentUserId = session?.user?.id ?? session?.session?.userId ?? null; const { orderedCompanies, persistOrder } = useCompanyOrder({ companies: sidebarCompanies, userId: currentUserId, }); const signOutMutation = useMutation({ mutationFn: () => authApi.signOut(), onSuccess: async () => { setOpen(false); if (isMobile) setSidebarOpen(false); await queryClient.invalidateQueries({ queryKey: queryKeys.auth.session }); }, }); function handleOpenChange(nextOpen: boolean) { if (!nextOpen) setIsEditingOrder(false); setOpen(nextOpen); } function closeNavigationChrome() { setOpen(false); setIsEditingOrder(false); if (isMobile) setSidebarOpen(false); } function selectCompany(company: Company) { const pathPrefix = location.pathname.split("/")[1]?.toUpperCase(); const isCompanyRoute = sidebarCompanies.some((sidebarCompany) => ( sidebarCompany.issuePrefix.toUpperCase() === pathPrefix )); const shouldLeaveCurrentRoute = company.id !== selectedCompany?.id && (location.pathname.startsWith("/instance/") || isCompanyRoute); setSelectedCompanyId(company.id); setOpen(false); if (isMobile) setSidebarOpen(false); if (shouldLeaveCurrentRoute) { navigate(`/${company.issuePrefix}/dashboard`); } } function addCompany() { setOpen(false); if (isMobile) setSidebarOpen(false); openOnboarding(); } const handleDragEnd = useCallback( (event: DragEndEvent) => { const { active, over } = event; if (!over || active.id === over.id) return; const ids = orderedCompanies.map((company) => company.id); const oldIndex = ids.indexOf(active.id as string); const newIndex = ids.indexOf(over.id as string); if (oldIndex === -1 || newIndex === -1) return; persistOrder(arrayMove(ids, oldIndex, newIndex)); }, [orderedCompanies, persistOrder], ); return (
Switch workspace
company.id)} strategy={verticalListSortingStrategy} > {orderedCompanies.map((company) => ( ))} {orderedCompanies.length === 0 ? ( No workspaces ) : null}
Add company... { if (isEditingOrder) { event.preventDefault(); return; } closeNavigationChrome(); }} > {selectedCompany ? `Invite people to ${selectedCompany.name}` : "Invite people"} { if (isEditingOrder) { event.preventDefault(); return; } closeNavigationChrome(); }} > Company settings {session?.session ? ( <> signOutMutation.mutate()} disabled={isEditingOrder || signOutMutation.isPending} > {signOutMutation.isPending ? "Signing out..." : "Sign out"} ) : null}
); }