"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
>
)
}