feat(ui): BOTS section in member list
Members API appends server bots (is_bot). Sidebar groups ONLINE / OFFLINE / BOTS. Bots get green BOT badge, no kick menu or profile. Mentions and DMs skip bots.
This commit is contained in:
@@ -57,7 +57,7 @@ interface MemberRowProps {
|
||||
|
||||
function MemberRow({ member, onProfileClick }: MemberRowProps) {
|
||||
const presence = usePresenceStore((s) => s.presences[member.id]);
|
||||
const status = presence?.status ?? member.status;
|
||||
const status = member.is_bot ? "online" : (presence?.status ?? member.status);
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const currentUser = useAuthStore((s) => s.user);
|
||||
const activeServerId = useServerStore((s) => s.activeServerId);
|
||||
@@ -72,14 +72,21 @@ function MemberRow({ member, onProfileClick }: MemberRowProps) {
|
||||
return (
|
||||
<div className="relative">
|
||||
<div
|
||||
onClick={() => onProfileClick(member.id)}
|
||||
className="w-full flex items-center gap-2 px-2 py-1 text-left hover:bg-gb-bg-t cursor-pointer group"
|
||||
onClick={() => {
|
||||
if (!member.is_bot) onProfileClick(member.id);
|
||||
}}
|
||||
className={`w-full flex items-center gap-2 px-2 py-1 text-left group ${
|
||||
member.is_bot ? "cursor-default" : "hover:bg-gb-bg-t cursor-pointer"
|
||||
}`}
|
||||
>
|
||||
<span className={statusColor(status)}>{statusIcon(status)}</span>
|
||||
<span className={`truncate ${usernameColor(status)} flex-1`}>
|
||||
<span className={`truncate ${member.is_bot ? "text-gb-green" : usernameColor(status)} flex-1`}>
|
||||
{member.nickname || member.display_name || member.username}
|
||||
</span>
|
||||
{!isSelf && (
|
||||
{member.is_bot && (
|
||||
<span className="text-gb-bg bg-gb-green px-0.5 font-mono text-[10px] shrink-0">BOT</span>
|
||||
)}
|
||||
{!isSelf && !member.is_bot && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -92,7 +99,7 @@ function MemberRow({ member, onProfileClick }: MemberRowProps) {
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{menuOpen && !isSelf && activeServerId && (
|
||||
{menuOpen && !isSelf && !member.is_bot && activeServerId && (
|
||||
<MemberContextMenu
|
||||
memberId={member.id}
|
||||
username={member.username}
|
||||
@@ -155,11 +162,13 @@ export function MemberList() {
|
||||
}
|
||||
}, [activeServerId, fetchMembers]);
|
||||
|
||||
const online = members.filter((m) => {
|
||||
const humans = members.filter((m) => !m.is_bot);
|
||||
const bots = members.filter((m) => m.is_bot);
|
||||
const online = humans.filter((m) => {
|
||||
const status = presences[m.id]?.status ?? m.status;
|
||||
return status !== "offline";
|
||||
});
|
||||
const offline = members.filter((m) => {
|
||||
const offline = humans.filter((m) => {
|
||||
const status = presences[m.id]?.status ?? m.status;
|
||||
return status === "offline";
|
||||
});
|
||||
@@ -178,7 +187,7 @@ export function MemberList() {
|
||||
)}
|
||||
{online.length > 0 && (
|
||||
<div className="mb-3">
|
||||
<div className="text-gb-fg-t text-xs uppercase mb-1">ONLINE</div>
|
||||
<div className="text-gb-fg-t text-xs uppercase mb-1">ONLINE — {online.length}</div>
|
||||
<div className="text-gb-fg-f text-xs mb-1">---</div>
|
||||
{online.map((m) => (
|
||||
<MemberRow key={m.id} member={m} onProfileClick={setProfileUserId} />
|
||||
@@ -186,14 +195,23 @@ export function MemberList() {
|
||||
</div>
|
||||
)}
|
||||
{offline.length > 0 && (
|
||||
<div>
|
||||
<div className="text-gb-fg-t text-xs uppercase mb-1">OFFLINE</div>
|
||||
<div className="mb-3">
|
||||
<div className="text-gb-fg-t text-xs uppercase mb-1">OFFLINE — {offline.length}</div>
|
||||
<div className="text-gb-fg-f text-xs mb-1">---</div>
|
||||
{offline.map((m) => (
|
||||
<MemberRow key={m.id} member={m} onProfileClick={setProfileUserId} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{bots.length > 0 && (
|
||||
<div>
|
||||
<div className="text-gb-fg-t text-xs uppercase mb-1">BOTS — {bots.length}</div>
|
||||
<div className="text-gb-fg-f text-xs mb-1">---</div>
|
||||
{bots.map((m) => (
|
||||
<MemberRow key={m.id} member={m} onProfileClick={setProfileUserId} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{profileUserId && (
|
||||
<UserProfileModal userId={profileUserId} onClose={() => setProfileUserId(null)} />
|
||||
|
||||
Reference in New Issue
Block a user