'use client' import { useState, useRef, useEffect, useCallback } from 'react' import { createPortal } from 'react-dom' import Link from 'next/link' import { useTranslations } from 'next-intl' import { cn } from '@/lib/utils' import { useCompany } from '@/contexts/CompanyContext' import { performCompanySwitch } from '@/lib/company/switch-client' import { useToast } from '@/components/ui/use-toast' import { Check, ChevronsUpDown, Plus, Loader2 } from 'lucide-react' export default function CompanySwitcher() { const { company, companies, isSandbox } = useCompany() const t = useTranslations('company_switcher') const { toast } = useToast() const [open, setOpen] = useState(false) const [isPending, setIsPending] = useState(false) const triggerRef = useRef(null) const dropdownRef = useRef(null) const [dropdownPos, setDropdownPos] = useState({ top: 0, left: 0 }) const updatePosition = useCallback(() => { if (!triggerRef.current || !dropdownRef.current) return const triggerRect = triggerRef.current.getBoundingClientRect() const dropdownRect = dropdownRef.current.getBoundingClientRect() const margin = 8 let top = triggerRect.bottom + 4 let left = triggerRect.left // Clamp right edge to viewport if (left + dropdownRect.width > window.innerWidth - margin) { left = Math.max(margin, window.innerWidth - dropdownRect.width - margin) } // If dropdown would go below viewport, show above trigger if (top + dropdownRect.height > window.innerHeight - margin) { top = Math.max(margin, triggerRect.top - dropdownRect.height - 4) } setDropdownPos({ top, left }) }, []) // Update position when opening (run twice: once to render, once to measure) useEffect(() => { if (!open) return const raf = requestAnimationFrame(() => updatePosition()) return () => cancelAnimationFrame(raf) }, [open, updatePosition]) // Close on outside click useEffect(() => { if (!open) return function handleClick(e: MouseEvent) { const target = e.target as Node if ( (!triggerRef.current || !triggerRef.current.contains(target)) && (!dropdownRef.current || !dropdownRef.current.contains(target)) ) { setOpen(false) } } document.addEventListener('mousedown', handleClick) return () => document.removeEventListener('mousedown', handleClick) }, [open]) // Close on Escape useEffect(() => { if (!open) return function handleKey(e: KeyboardEvent) { if (e.key === 'Escape') setOpen(false) } document.addEventListener('keydown', handleKey) return () => document.removeEventListener('keydown', handleKey) }, [open]) const handleSwitch = async (companyId: string) => { if (company && companyId === company.id) { setOpen(false) return } setIsPending(true) // Shared switch-and-reload mechanism (lib/company/switch-client), same // path as the sidebar user-menu flyout. const result = await performCompanySwitch(companyId) if (result?.error) { setIsPending(false) toast({ title: t(result.error === 'not_member' ? 'error_no_access' : 'error_switch_failed'), variant: 'destructive', }) } } // Always allow opening the dropdown (to show "Lägg till företag") const hasMultiple = companies.length > 1 // No companies yet: show a direct "Lägg till företag" link instead of // the switcher so the user can still create one. Hidden in sandbox mode. if (!company && companies.length === 0) { if (isSandbox) return null return ( {t('add_company')} ) } return (
{open && createPortal(
{companies.length > 0 && ( <> {hasMultiple && (

{t('company_label')}

)}
{companies.map(({ company: c, role }) => ( ))}
)} {!isSandbox && (
0 && 'border-t border-border/40 mt-1 pt-1', 'px-1')}> setOpen(false)} className="flex items-center gap-2 px-2.5 py-2 text-[13px] text-muted-foreground hover:text-foreground hover:bg-muted/40 rounded-md transition-colors md:whitespace-nowrap" > {t('add_company')}
)}
, document.body )}
) }