MemberListPanel.tsx raw

   1  import { useChat } from '@/providers/ChatProvider'
   2  import { useFetchProfile } from '@/hooks/useFetchProfile'
   3  import { useSecondaryPage } from '@/PageManager'
   4  import { Pubkey } from '@/domain'
   5  import { X, MessageSquare, Shield, Crown } from 'lucide-react'
   6  import { Button } from '../ui/button'
   7  
   8  export default function MemberListPanel({ onClose }: { onClose: () => void }) {
   9    const { currentChannel, channelParticipants, channelMods } = useChat()
  10  
  11    if (!currentChannel) return null
  12  
  13    // Sort: owner first, then mods, then everyone else
  14    const sorted = [...channelParticipants].sort((a, b) => {
  15      const aOwner = a === currentChannel.creator ? 0 : 1
  16      const bOwner = b === currentChannel.creator ? 0 : 1
  17      if (aOwner !== bOwner) return aOwner - bOwner
  18      const aMod = channelMods.includes(a) ? 0 : 1
  19      const bMod = channelMods.includes(b) ? 0 : 1
  20      return aMod - bMod
  21    })
  22  
  23    return (
  24      <div className="absolute inset-y-0 right-0 z-20 w-56 bg-background border-l overflow-y-auto">
  25        <div className="flex items-center justify-between p-2 border-b">
  26          <span className="text-xs font-semibold">Members ({sorted.length})</span>
  27          <Button variant="ghost" size="icon" className="size-6" onClick={onClose}>
  28            <X className="size-3.5" />
  29          </Button>
  30        </div>
  31        <div className="py-1">
  32          {sorted.map((pk) => (
  33            <MemberItem
  34              key={pk}
  35              pubkey={pk}
  36              isOwner={pk === currentChannel.creator}
  37              isMod={channelMods.includes(pk)}
  38            />
  39          ))}
  40        </div>
  41      </div>
  42    )
  43  }
  44  
  45  function MemberItem({
  46    pubkey,
  47    isOwner,
  48    isMod
  49  }: {
  50    pubkey: string
  51    isOwner: boolean
  52    isMod: boolean
  53  }) {
  54    const { profile } = useFetchProfile(pubkey)
  55    const { push } = useSecondaryPage()
  56    const pk = Pubkey.tryFromString(pubkey)
  57    const displayName = profile?.username || pk?.formatNpub(8) || pubkey.slice(0, 12)
  58  
  59    return (
  60      <div className="group flex items-center gap-2 px-2 py-1 hover:bg-muted/50 text-xs">
  61        <div className="size-5 rounded-full bg-muted overflow-hidden shrink-0">
  62          {profile?.avatar && <img src={profile.avatar} alt="" className="w-full h-full object-cover" />}
  63        </div>
  64        <span className="font-medium truncate flex-1">{displayName}</span>
  65        {isOwner && <span title="Owner"><Crown className="size-3 text-primary shrink-0" /></span>}
  66        {isMod && !isOwner && <span title="Mod"><Shield className="size-3 text-muted-foreground shrink-0" /></span>}
  67        <button
  68          className="opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-foreground shrink-0"
  69          onClick={() => push(`/dm/${pubkey}`)}
  70          title="Send DM"
  71        >
  72          <MessageSquare className="size-3" />
  73        </button>
  74      </div>
  75    )
  76  }
  77