import { useState, useEffect, useRef } from "react"; import { useLocation } from "react-router"; import { Dropdown } from "../ui/dropdown/Dropdown"; import { DropdownItem } from "../ui/dropdown/DropdownItem"; import { fetchSites, Site, setActiveSite as apiSetActiveSite } from "../../services/api"; import { useToast } from "../ui/toast/ToastContainer"; import { useSiteStore } from "../../store/siteStore"; import { useAuthStore } from "../../store/authStore"; /** * SiteSwitcher Component * * Shows a dropdown menu with all active sites for the user. * Should only be visible on planner and writer pages (and their submodules). * * Paths where switcher should be shown: * - /planner (dashboard and all subpages) * - /writer (dashboard and all subpages) * * Paths where switcher should be hidden: * - /settings (including /settings/sites) * - /dashboard * - /analytics * - /schedules * - /thinker * - /signin, /signup */ // Paths where switcher should be shown (planner and writer modules) const SITE_SWITCHER_SHOWN_PATHS = [ '/planner', '/writer', ]; // Paths where switcher should be hidden (override shown paths) const SITE_SWITCHER_HIDDEN_PATHS = [ '/settings', '/dashboard', '/analytics', '/schedules', '/thinker', ]; interface SiteSwitcherProps { hiddenPaths?: string[]; // Optional override for hidden paths } export default function SiteSwitcher({ hiddenPaths }: SiteSwitcherProps) { const location = useLocation(); const toast = useToast(); const { activeSite, setActiveSite, loadActiveSite } = useSiteStore(); const { user, refreshUser, isAuthenticated } = useAuthStore(); const [isOpen, setIsOpen] = useState(false); const [sites, setSites] = useState([]); const [loading, setLoading] = useState(true); const buttonRef = useRef(null); // Determine if switcher should be shown const hiddenPathsToUse = hiddenPaths || SITE_SWITCHER_HIDDEN_PATHS; const shownPaths = SITE_SWITCHER_SHOWN_PATHS; let shouldHide = false; if (shownPaths.length > 0) { // If shown paths are defined, only show on those paths shouldHide = !shownPaths.some(path => location.pathname.startsWith(path)); } else { // Otherwise, hide on explicitly hidden paths shouldHide = hiddenPathsToUse.some(path => location.pathname.startsWith(path)); } // Refresh user data when component mounts or user changes // This ensures we have latest account/plan info for proper site filtering useEffect(() => { if (isAuthenticated && user) { // Refresh user data to get latest account/plan changes // This is important so site filtering works correctly refreshUser().catch((error) => { // Silently fail - user might still be valid, just couldn't refresh console.debug('SiteSwitcher: Failed to refresh user (non-critical):', error); }); } }, [isAuthenticated]); // Only refresh when auth state changes, not on every render useEffect(() => { if (shouldHide) { setLoading(false); return; } loadSites(); // Load active site from store (only once, not on every render) if (!activeSite) { loadActiveSite(); } }, [shouldHide, location.pathname, user?.account?.id]); // Also reload when user's account changes const loadSites = async () => { try { setLoading(true); const response = await fetchSites(); // Filter to only show active sites in the switcher const activeSites = (response.results || []).filter(site => site.is_active); setSites(activeSites); } catch (error: any) { console.error('Failed to load sites:', error); toast.error(`Failed to load sites: ${error.message}`); } finally { setLoading(false); } }; const handleSiteSelect = async (siteId: number) => { try { await apiSetActiveSite(siteId); const selectedSite = sites.find(s => s.id === siteId); if (selectedSite) { // Update the global site store - this will dispatch siteChanged event setActiveSite(selectedSite); toast.success(`Switched to "${selectedSite.name}"`); } setIsOpen(false); } catch (error: any) { toast.error(`Failed to switch site: ${error.message}`); } }; function toggleDropdown() { setIsOpen(!isOpen); } // Don't render if should be hidden if (shouldHide) { return null; } // Don't render if loading or no sites if (loading || sites.length === 0) { return null; } return (
setIsOpen(false)} anchorRef={buttonRef} placement="bottom-left" className="w-64 p-2" > {sites.map((site) => ( handleSiteSelect(site.id)} className={`flex items-center gap-3 px-3 py-2 font-medium rounded-lg text-sm text-left ${ activeSite?.id === site.id ? "bg-blue-50 text-blue-700 dark:bg-blue-500/20 dark:text-blue-300" : "text-gray-700 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300" }`} > {site.name} {activeSite?.id === site.id && ( )} ))}
); }