import React, { useState, useRef, useEffect } from 'react'; import { createPortal } from 'react-dom'; import { useNavigate } from 'react-router-dom'; import { useTenant } from '../../stores/tenant.store'; import { useToast } from '../../hooks/ui/useToast'; import { ChevronDown, Building2, Check, AlertCircle, Plus } from 'lucide-react'; interface TenantSwitcherProps { className?: string; showLabel?: boolean; } export const TenantSwitcher: React.FC = ({ className = '', showLabel = true, }) => { const navigate = useNavigate(); const [isOpen, setIsOpen] = useState(false); const [dropdownPosition, setDropdownPosition] = useState<{ top: number; left: number; right?: number; width: number; isMobile: boolean; }>({ top: 0, left: 0, width: 288, isMobile: false }); const dropdownRef = useRef(null); const buttonRef = useRef(null); const { currentTenant, availableTenants, isLoading, error, switchTenant, loadUserTenants, clearError, } = useTenant(); const { success: showSuccessToast, error: showErrorToast } = useToast(); // Load tenants on mount useEffect(() => { if (!availableTenants) { loadUserTenants(); } }, [availableTenants, loadUserTenants]); // Handle click outside to close dropdown useEffect(() => { const handleClickOutside = (event: MouseEvent | TouchEvent) => { const target = event.target as Node; if ( dropdownRef.current && !dropdownRef.current.contains(target) && !buttonRef.current?.contains(target) ) { setIsOpen(false); } }; const handleEscape = (event: KeyboardEvent) => { if (event.key === 'Escape') { setIsOpen(false); } }; if (isOpen) { document.addEventListener('mousedown', handleClickOutside); document.addEventListener('touchstart', handleClickOutside); document.addEventListener('keydown', handleEscape); } return () => { document.removeEventListener('mousedown', handleClickOutside); document.removeEventListener('touchstart', handleClickOutside); document.removeEventListener('keydown', handleEscape); }; }, [isOpen]); // Recalculate position on window resize useEffect(() => { const handleResize = () => { if (isOpen) { calculateDropdownPosition(); } }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, [isOpen]); // Calculate dropdown position const calculateDropdownPosition = () => { if (!buttonRef.current) return; const buttonRect = buttonRef.current.getBoundingClientRect(); const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const isMobile = viewportWidth < 768; // md breakpoint if (isMobile) { // On mobile, use full width with margins and position from top // Check if dropdown would go off bottom of screen const dropdownHeight = Math.min(400, viewportHeight * 0.7); // Max 70vh const spaceBelow = viewportHeight - buttonRect.bottom - 8; const shouldPositionAbove = spaceBelow < dropdownHeight && buttonRect.top > dropdownHeight; setDropdownPosition({ top: shouldPositionAbove ? buttonRect.top - dropdownHeight - 8 : buttonRect.bottom + 8, left: 16, // 1rem margin from screen edge right: 16, // For full width calculation width: viewportWidth - 32, // Full width minus margins isMobile: true, }); } else { // Desktop positioning - align right edge of dropdown with right edge of button const dropdownWidth = 320; // w-80 (20rem * 16px) - slightly wider for desktop let left = buttonRect.right - dropdownWidth; // Ensure dropdown doesn't go off the left edge of the screen if (left < 16) { left = 16; } // Ensure dropdown doesn't go off the right edge if (left + dropdownWidth > viewportWidth - 16) { left = viewportWidth - dropdownWidth - 16; } setDropdownPosition({ top: buttonRect.bottom + 8, left: left, width: dropdownWidth, isMobile: false, }); } }; // Handle dropdown open/close const toggleDropdown = () => { if (!isOpen) { calculateDropdownPosition(); } setIsOpen(!isOpen); }; // Handle tenant switch const handleTenantSwitch = async (tenantId: string) => { if (tenantId === currentTenant?.id) { setIsOpen(false); return; } const success = await switchTenant(tenantId); setIsOpen(false); if (success) { const newTenant = availableTenants?.find(t => t.id === tenantId); showSuccessToast(`Switched to ${newTenant?.name}`, { title: 'Tenant Switched' }); } else { showErrorToast(error || 'Failed to switch tenant', { title: 'Switch Failed' }); } }; // Handle retry loading tenants const handleRetry = () => { clearError(); loadUserTenants(); }; // Handle creating new tenant const handleCreateNewTenant = () => { setIsOpen(false); navigate('/app/onboarding'); }; // Don't render if no tenants available if (!availableTenants || availableTenants.length === 0) { return null; } // Don't render if only one tenant if (availableTenants.length === 1) { return showLabel ? (
{currentTenant?.name}
) : null; } return (
{/* Trigger Button */} {/* Dropdown Menu - Rendered in portal to avoid stacking context issues */} {isOpen && createPortal(
{/* Header */}

Organizations

{/* Error State */} {error && (
{error}
)} {/* Tenant List */}
{availableTenants.map((tenant) => ( ))}
{/* Footer */}
, document.body )} {/* Loading Overlay */} {isLoading && (
)}
); };