import React, { useState, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { useServerStore } from '../../stores/serverStore'; import { useChatStore } from '../../stores/chatStore'; import { useUIStore } from '../../stores/uiStore'; import { useInstanceStore } from '../../stores/instanceStore'; import { getServerGradient, HOME_GRADIENT } from '../../utils/gradients'; interface SidebarItemProps { id: string; name: string; icon?: string | null; active: boolean; onClick: () => void; type?: 'server' | 'dm' | 'action'; actionType?: 'add' | 'join' | 'explore'; hasUnread?: boolean; dimmed?: boolean; } function SidebarItem({ id, name, icon, active, onClick, type = 'server', actionType, hasUnread, dimmed }: SidebarItemProps) { const [isHovered, setIsHovered] = useState(false); const firstLetter = name.charAt(0).toUpperCase(); const getPillHeight = () => { if (active) return 'h-8'; if (isHovered) return 'h-4'; if (hasUnread && !active) return 'h-2'; return 'h-2 scale-0'; }; const backgroundStyle = useMemo((): React.CSSProperties | undefined => { if (type === 'action') { return { background: isHovered ? 'rgba(134, 239, 172, 0.12)' : 'rgba(255, 255, 255, 0.04)', }; } if (type === 'dm') { return { background: HOME_GRADIENT.gradient }; } // Server type — if it has a custom icon image, no gradient needed if (icon) return undefined; const serverGrad = getServerGradient(id, name); return { background: serverGrad.gradient }; }, [type, id, name, icon, isHovered]); const getButtonClasses = () => { const base = 'w-10 h-10 flex items-center justify-center duration-200 overflow-hidden [transition:border-radius_0.2s,background_0.2s,color_0.2s]'; if (type === 'dm') { return `${base} text-white ${active ? 'rounded-[13px]' : 'rounded-[20px] hover:rounded-[13px]'}`; } if (type === 'action') { return `${base} rounded-[20px] hover:rounded-[13px] text-accent-mint`; } if (icon) { return `${base} ${active ? 'rounded-[13px]' : 'rounded-[20px] hover:rounded-[13px]'}`; } return `${base} text-white ${active ? 'rounded-[13px]' : 'rounded-[20px] hover:rounded-[13px]'}`; }; return (
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > {/* Pill Indicator */} {(type === 'server' || type === 'dm') && (
)}
); } export function ServerSidebar() { const servers = useServerStore((s) => s.servers); const currentServerId = useServerStore((s) => s.currentServerId); const setCurrentServer = useServerStore((s) => s.setCurrentServer); const channelToServerMap = useServerStore((s) => s.channelToServerMap); const dmChannels = useServerStore((s) => s.dmChannels); const showDms = useUIStore((s) => s.showDms); const setShowDms = useUIStore((s) => s.setShowDms); const showExplore = useUIStore((s) => s.showExplore); const setShowExplore = useUIStore((s) => s.setShowExplore); const openModal = useUIStore((s) => s.openModal); const addToast = useUIStore((s) => s.addToast); const setCurrentChannel = useChatStore((s) => s.setCurrentChannel); const unreadChannels = useChatStore((s) => s.unreadChannels); const instances = useInstanceStore((s) => s.instances); const navigate = useNavigate(); // Group servers by origin const groupedServers = useMemo(() => { const home = servers.filter(s => !(s as any)._instanceOrigin); const remoteMap = new Map(); for (const s of servers) { const origin = (s as any)._instanceOrigin; if (!origin) continue; const list = remoteMap.get(origin) || []; list.push(s); remoteMap.set(origin, list); } return { home, remoteGroups: Array.from(remoteMap.entries()) }; }, [servers]); // Set of disconnected origins const disconnectedOrigins = useMemo(() => { const set = new Set(); for (const inst of instances) { if (inst.status === 'disconnected' || inst.status === 'error') { set.add(inst.origin); } } return set; }, [instances]); // Compute which servers have unread channels const unreadServerIds = useMemo(() => { const ids = new Set(); for (const channelId of unreadChannels) { const serverId = channelToServerMap.get(channelId); if (serverId) ids.add(serverId); } return ids; }, [unreadChannels, channelToServerMap]); // Check if any DM channels are unread const hasDmUnread = useMemo(() => { for (const dm of dmChannels) { if (unreadChannels.has(dm.id)) return true; } return false; }, [unreadChannels, dmChannels]); const handleServerClick = (serverId: string) => { const server = servers.find(s => s.id === serverId); const origin = (server as any)?._instanceOrigin; if (origin && disconnectedOrigins.has(origin)) { const inst = instances.find(i => i.origin === origin); addToast(`Reconnecting to ${inst?.label || 'remote instance'}...`, 'warning', 4000); return; } setCurrentServer(serverId); setShowDms(false); setShowExplore(false); navigate(`/channels/${serverId}`); }; const handleDmClick = () => { setShowDms(true); setCurrentServer(null); setCurrentChannel(null); navigate('/channels/@me'); }; const handleExploreClick = () => { setShowExplore(true); setCurrentServer(null); setCurrentChannel(null); navigate('/channels/@me'); }; return ( ); }