feat(frontend): split members sidebar into channel + guild sections
When a channel is selected the Members panel shows 'In channel — N' (that channel's members) above 'Guild — N' (all guild members). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,7 @@ export default function ChatPage() {
|
||||
const { session, logout, ensureFreshToken, refreshGuilds, updateName } = useAuth()
|
||||
const [selectedGuildId, setSelectedGuildId] = useState('')
|
||||
const [selectedChannelId, setSelectedChannelId] = useState('')
|
||||
const [channelMemberIds, setChannelMemberIds] = useState<string[]>([])
|
||||
const [channels, setChannels] = useState<GuildChannel[]>([])
|
||||
const [guildDbId, setGuildDbId] = useState('')
|
||||
const [members, setMembers] = useState<MemberItem[]>([])
|
||||
@@ -134,6 +135,20 @@ export default function ChatPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadChannelMembers() {
|
||||
if (!guild || !guildToken || !selectedChannelId) {
|
||||
setChannelMemberIds([])
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await guildApi().get(`/channels/${selectedChannelId}/members`)
|
||||
const list = Array.isArray(res.data) ? (res.data as Array<{ userId: string }>) : []
|
||||
setChannelMemberIds(list.map((x) => x.userId))
|
||||
} catch {
|
||||
setChannelMemberIds([])
|
||||
}
|
||||
}
|
||||
|
||||
async function pullMessages() {
|
||||
if (!selectedChannelId || !guild || !guildToken) return
|
||||
setLoading(true)
|
||||
@@ -242,6 +257,7 @@ export default function ChatPage() {
|
||||
await guildApi().post(`/channels/${c.id}/join`)
|
||||
await loadChannels()
|
||||
await loadMembers()
|
||||
await loadChannelMembers()
|
||||
} catch {
|
||||
setError('Failed to join channel')
|
||||
}
|
||||
@@ -257,6 +273,7 @@ export default function ChatPage() {
|
||||
}
|
||||
await loadChannels()
|
||||
await loadMembers()
|
||||
await loadChannelMembers()
|
||||
} catch {
|
||||
setError('Failed to leave channel')
|
||||
}
|
||||
@@ -290,6 +307,7 @@ export default function ChatPage() {
|
||||
|
||||
useEffect(() => {
|
||||
void pullMessages()
|
||||
void loadChannelMembers()
|
||||
}, [selectedChannelId])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -318,6 +336,23 @@ export default function ChatPage() {
|
||||
const authorLabel = (uid?: string) =>
|
||||
uid ? (uid === session?.user.id ? session?.user.name || 'You' : nameById.get(uid) || uid.slice(0, 8)) : 'unknown'
|
||||
|
||||
const guildById = new Map(members.map((m) => [m.userId, m]))
|
||||
const channelMembers = channelMemberIds.map(
|
||||
(id) => guildById.get(id) ?? { userId: id, email: '', name: '', status: 'active' },
|
||||
)
|
||||
const renderMember = (m: { userId: string; name?: string; email?: string }) => {
|
||||
const label = m.name || m.email || m.userId.slice(0, 8)
|
||||
return (
|
||||
<div key={m.userId} className="member-row">
|
||||
<div className="avatar dot">{initials(label)}</div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<span className="nm">{label}</span>
|
||||
{m.userId === session?.user.id ? <span className="you">you</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="dc-shell">
|
||||
<nav className="dc-rail">
|
||||
@@ -479,18 +514,25 @@ export default function ChatPage() {
|
||||
|
||||
{showMembers ? (
|
||||
<aside className="dc-members">
|
||||
<div className="dc-section-label">
|
||||
<span>Members — {members.length}</span>
|
||||
</div>
|
||||
{members.map((m) => (
|
||||
<div key={m.userId} className="member-row">
|
||||
<div className="avatar dot">{initials(m.name || m.email)}</div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<span className="nm">{m.name || m.email}</span>
|
||||
{m.userId === session?.user.id ? <span className="you">you</span> : null}
|
||||
{currentChannel ? (
|
||||
<>
|
||||
<div className="dc-section-label">
|
||||
<span>In channel — {channelMembers.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{channelMembers.map(renderMember)}
|
||||
<div className="dc-section-label" style={{ marginTop: 14 }}>
|
||||
<span>Guild — {members.length}</span>
|
||||
</div>
|
||||
{members.map(renderMember)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="dc-section-label">
|
||||
<span>Members — {members.length}</span>
|
||||
</div>
|
||||
{members.map(renderMember)}
|
||||
</>
|
||||
)}
|
||||
</aside>
|
||||
) : null}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user