"use client" import * as React from "react" import { IconX, IconCrown, IconShield } from "@tabler/icons-react" import { getChannelMembersWithPresence } from "@/app/actions/presence" import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { ScrollArea } from "@/components/ui/scroll-area" import { Sheet, SheetContent, SheetHeader, SheetTitle, } from "@/components/ui/sheet" import { cn } from "@/lib/utils" type MemberWithPresence = { readonly id: string readonly displayName: string | null readonly avatarUrl: string | null readonly role: string readonly status: string readonly statusMessage: string | null } type MemberSidebarProps = { channelId: string isOpen: boolean onClose: () => void } type MemberGroupProps = { readonly title: string readonly members: readonly MemberWithPresence[] readonly statusColor: string } function getStatusColor(status: string): string { switch (status) { case "online": return "bg-green-500" case "idle": return "bg-yellow-500" case "dnd": return "bg-red-500" default: return "bg-gray-400" } } function getInitials(name: string | null): string { if (!name) return "?" const parts = name.trim().split(/\s+/) if (parts.length === 1) { return parts[0].charAt(0).toUpperCase() } return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase() } function getRoleBadgeVariant( role: string ): "default" | "secondary" | "outline" { switch (role) { case "owner": return "default" case "moderator": return "secondary" default: return "outline" } } function RoleIcon({ role }: { readonly role: string }) { if (role === "owner") { return } if (role === "moderator") { return } return null } function MemberGroup({ title, members, statusColor }: MemberGroupProps) { if (members.length === 0) return null return (

{title} — {members.length}

    {members.map((member) => (
  • ))}
) } function MemberListContent({ channelId, isOpen, }: { readonly channelId: string readonly isOpen: boolean }) { const [members, setMembers] = React.useState<{ online: MemberWithPresence[] idle: MemberWithPresence[] dnd: MemberWithPresence[] offline: MemberWithPresence[] } | null>(null) const [loading, setLoading] = React.useState(true) const [error, setError] = React.useState(null) React.useEffect(() => { let mounted = true async function fetchMembers() { try { const result = await getChannelMembersWithPresence(channelId) if (!mounted) return if (result.success) { setMembers(result.data) } else { setError(result.error) } } catch (err) { if (!mounted) return setError(err instanceof Error ? err.message : "Failed to load members") } finally { if (mounted) setLoading(false) } } fetchMembers() // 10-second polling interval when sidebar is open let pollInterval: ReturnType | null = null if (isOpen) { pollInterval = setInterval(fetchMembers, 10_000) } return () => { mounted = false if (pollInterval) { clearInterval(pollInterval) } } }, [channelId, isOpen]) if (loading) { return (
) } if (error) { return (
{error}
) } if (!members) { return (
No members found
) } const totalMembers = members.online.length + members.idle.length + members.dnd.length + members.offline.length return (
{totalMembers} member{totalMembers !== 1 ? "s" : ""}
) } export function MemberSidebar({ channelId, isOpen, onClose, }: MemberSidebarProps) { return ( <> {/* Desktop sidebar */} {/* Mobile sheet */} !open && onClose()}>
Members
) }